c++问题记录

c++多重继承中的内存布局

c/c++

this指针,静态,复合,委托,继承,虚函数
迭代器,分配器,

  1. 使用初始化列表
  2. 善用const引用

数组的大小不必在编译时确定

逗号表达式:逗号表达式的结果是其最右边表达式的值

1
cout<<4,cout<<5;//输出45;


fork();
1.fork()函数会把它所在语句以后的语句复制到一个子进程里,单独执行。
2.如果printf函数最后没有”\n”,则输出缓冲区不会被立即清空,而fork函数会把输出缓冲区里的内容也都复制到子进程里。
所以,父进程和子进程各输出2个Hello,共4个。
如果第一个printf(“Hello”);写成printf(“Hello\n”);,则只会输出3个Hello,父进程2个,子进程1个。

1
2
3
4
5
int main() {//hello输出4次
printf("Hello");
fork();
printf("Hello");
}

z = x++ - –y b/a;
上面这个式子,在C中其实是理解成 z = ((x++)–)-y
b / a;
所以其实是z = ((5++)–)-(-10)2/4 = 5-(-5) = 10;
大家不要被题目故意留的空格迷惑了,编译器是不会理那些空格的,它看起来就是z=x++—y
b/a,而C语言读取符号时的词法分析遵循的规则叫“贪心法”,就它读取每一个符号都尽量包括多的字符,赋值号右边的式子从左往右读,第一个字符“x”,然后它会尝试读下一个字符看“x+”能不能组成一个符号,若不行,则读下一个字符,尝试“++”能不能组成一个符号,显然可以,然后再接着往下读“-”,尝试下一个字符“-”,发现“–”也能组成一个字符。
所以上式它会读成 z = ((x++)–)-y * b / a;

构造函数放在private区 (Singleton)

相同class的各个objects互为friends

任何成员函数都有个隐藏的参数是 this

临时对象 return class(int x,int y); 不能用引用接受


重载输出运算符

1
2
3
4
ostream& operator << (ostream& os,const complex& x)
{
return os << '('<<real(x)<<','<<imag(x)<<')';//改变os状态
}


“a+=b”是不申请空间的,直接加给左边的a
“a=a+b”是要申请一个新的空间再赋值给a的


如果类里面有指针成员变量,必须有拷贝构造函数和拷贝赋值函数,
浅拷贝:不写拷贝构造函数,只复制指针,指向的区域没有变
深拷贝:拷贝构造函数,整个指针指向的值的区域都复制

拷贝构造

1
2
3
4
5
6
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}

拷贝赋值

1
2
3
4
5
6
7
8
9
10
11
inline
String& String::operator=(const String& str)
{
if (this == &str)//检测自我赋值,不用进行下面的操作
return *this;

delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}


new/delete

Complex* pc=new Complex(1,2);

void mem=operator new(sizeof(Complex)); //调用malloc分配内存
pc=static_cast<complex
>(mem); //转型
pc->Complex::Complex(1,2); //构造函数

delete pc;

Complex::~Complex(pc); //析构函数
operator delete(pc); //调用free(pc),释放内存


复合的class,构造函数由内而外,析构函数由外而内


在 C 程序中,赋值运算符的优先级最低 (f)
逗号运算符的优先级最低


定义一个二维数组,可以省略第一维的定义,但不能省略第二维的定义。
系统根据初始化的数据个数和第2维的长度可以确定第一维的长度。
int a[][3]={ 1,2,3,4,5,6};
a数组的第一维的定义被省略,初始化数据共6个,第二维的长度为3,即每行3个数,所以a数组的第一维是2。
一般,省略第一维的定义时,第一维的大小按如下规则确定:
初值个数能被第二维整除,所得的商就是第一维的大小;若不能整除,则第一维的大小为商再加1。例如: int a[][3]={ 1,2,3,4};等价于:int a[2][3]={ 1,2,3,4};


>
char strcpy(char dest, const char src) 把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间
void
memcpy(void dest, const void src, size_t n); 从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中
void memmove( void dest, const void src, size_t count ); 由src所指内存区域复制count个字节到dest所指内存区域。
void
memset(void *s, int ch, size_t n); 将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s


cdecl fastcall与stdcall,三者都是调用约定(Calling convention),它决定以下内容:1)函数参数的压栈顺序,2)由调用者还是被调用者把参数弹出栈,3)以及产生函数修饰名的方法。
1、
stdcall调用约定:函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈。
2、_cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。注意:对于可变参数的成员函数,始终使用cdecl的转换方式。
3、
fastcall调用约定:它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈)。
4、thiscall仅仅应用于”C++”成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。
5、nakedcall采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。


数组指针更像是指针的一个真子集,主要用于数组,所以范围更窄,而指针是可以用于数组乃至更广的地方,同时由于数组指针针对的是数组,所以有些方面可能会有特权(这是编译器实现的),比如使用sizeof(数组指针)和sizeof(指针),前者是数组长度*指针的范围(以字节为单位,win32程序就是4),而指针就只是指针的范围。


c++中规定,重载运算符必须和用户定义的自定义类型的对象一起使用。


在构造函数中需要初始化列表初始化的有如下三种情况
1.带有const修饰的类成员 ,如const int a ;
2.引用成员数据,如 int& p;
3.带有引用的类变量,如:

1
2
3
4
5
6
7
8
class A {
private:
int &a;
};
class B{
private:
A c;
}

这里的c需要用初始化列表进行初始化。


逻辑与(&&)和逻辑或(||)存在“短路”现象。逻辑运算符||,左侧为非零,右侧就不用判断了


c++不允许后加加多次 ++++i(T);i++++(F);


初始化指针时所创建的字符串常量被定义为只读char* str=”hello”;不能修改里面的字符


运算符优先级

7&3+12的值是15 (F)+优先级高于&(按位与)


基本类型的对象没有析构函数,所以回收基本类型组成的数组空间用 delete 和 delete[] 都是应该可以的;但是对于类对象数组,只能用 delete[]。对于 new 的单个对象,只能用 delete 不能用 delete[] 回收空间。


在《C和指针》这本书里有介绍,int p1=new int[10];该语句给指针p1分配内存,没有对指针初始化, int p2=new int10;该语句给指针p2分配内存,并初始化指针*


explict关键字 (类的强制类型转换)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Fraction//分数
{
public:
Fraction(int num,int den=1)
:m_numberator(num),m_denominator(den){}
operator double() const {//不用定义返回值。
return (double)(m_numberator/m_denominator);
}
private:
int m_numberator;//分子
int m_denominator;//分母
}

Fraction f(3,5);
double d=4+f;//会自动强制类型转换,因为定义了double的重载

non-explicit-one-argument ctor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Fraction//分数
{
public:
Fraction(int num,int den=1)
:m_numberator(num),m_denominator(den){}
Fraction operator + (const Fraction& f){
return Fraction(...);
}
private:
int m_numberator;//分子
int m_denominator;//分母
}

Fraction f(3,5);
Fraction d=f+4;//调用构造函数将4转换成Fraction(4,1);然后调用operator+

explicit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Fraction//分数
{
public:
explicit Fraction(int num,int den=1)//加入关键字
:m_numberator(num),m_denominator(den){}
Fraction operator + (const Fraction& f){
return Fraction(...);
}
private:
int m_numberator;//分子
int m_denominator;//分母
}

Fraction f(3,5);
Fraction d=f+4;//【Error】 不能转换


智能指针

1
2
3
4
5
6
7
8
9
10
11
12
13
template<class T>
clss shared_ptr
{
public:
shared_ptr(T* p):px(p) {}
T& operator*() const
{ return * px;}
T* operator->() const
{ return px;}
private:
T* px;
long* pn;
}

STL

C++ STL 的实现:
1.vector 底层数据结构为数组 ,支持快速随机访问
2.list 底层数据结构为双向链表,支持快速增删
3.deque 底层数据结构为一个中央控制器和多个缓冲区,详细见STL源码剖析P146,支持首尾(中间不能)快速增删,也支持随机访问
4.stack 底层一般用23实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
5.queue 底层一般用23实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
6.45是适配器,而不叫容器,因为是对容器的再封装
7.priority_queue 的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现
8.set 底层数据结构为红黑树,有序,不重复
9.multiset 底层数据结构为红黑树,有序,可重复
10.map 底层数据结构为红黑树,有序,不重复
11.multimap 底层数据结构为红黑树,有序,可重复
12.hash_set(unordered_set) 底层数据结构为hash表,无序,不重复
13.hash_multiset(unordered_multiset) 底层数据结构为hash表,无序,可重复
14.hash_map(unordered_map) 底层数据结构为hash表,无序,不重复
15.hash_multimap(unordered_multimap) 底层数据结构为hash表,无序,可重复


volatile

volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。例如:

1
2
3
4
5
volatile int i=10;
int a = i;
...
// 其他代码,并未明确告诉编译器,对 i 进行过操作
int b = i;

volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。


多态与virtual

C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数

1:用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。

2:存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。

3:多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性。

4:多态用虚函数来实现,结合动态绑定.

5: 纯虚函数是虚函数再加上 = 0;

6:抽象类是指包括至少一个纯虚函数的类。
虚函数

纯虚函数
纯虚函数格式:virtual <类型> <函数名> (<参数表>) = 0;纯虚函数要以=0 结尾,并且virtual 放在最前面

本文标题:c++问题记录

文章作者:Hengliy

发布时间:2018年02月08日 - 22:02

最后更新:2018年03月08日 - 13:03

原始链接:http://hengliy.github.io/2018/02/08/c++问题记录/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。