看了国嵌的c++深入实践视频,唐老师讲的非常好,感谢唐老师,记录一下
1.什么是符号表
符号表是编译器在编译过程中产生的关于源程序中语法符号的数据结构。
如常量表、变量名表、数组名表、函数表等
符号表是编译器自用的内部数据结构
符号表不会进入最终产生的可执行程序中
***只有用字面量初始化的const常量才会进入符号表
Const int i =1;
对const常量进行引用取地址会导致编译器为其分配空间
虽然const常量被分配了空间,但是这个空间中的值不会被使用
使用其它变量初始化的const常量仍然是只读变量
被volatile修饰的const常量不会进入符号表,退化为只读变量,每次访问都从内存中取值
const引用的类型与初始化变量的类型
相同:使初始化变量成为只读变量
不同:生成一个新的只读变量,其初始值与初始化变量相同
2.引用与指针的区别
操作引用就还操作引用的变量本身
指针是一个变量,其值是一个内存地址,可以通过指针访问对应内存地址中的值
引用是一个变量的新名字,所有对引用的操作(赋值,取地址),都会传递到其引用的变量上。
指针可以被const修饰成常量或者只读变量
Const 引用使其引用的变量具有只读属性
指针就是变量,不需要进行初始化,也可以指向不同的地址,但是引用不可以。
3.重载的问题
当重载函数使用字面量进行参数传递时,编译器可能会不能识别改用哪个重载函数
Func(1,2,3)
编译器不能判断它为char型还是int 型
4.malloc与free和new与delete有什么区别
malloc和free是库函数,以字节为单位申请堆内存,,不会对堆空间进行初始化,
new和delete是关键字,以类型为单位申请堆内存,,可以对堆空间在申请的时候进行初始化。
malloc和free单纯的对内存进行申请与释放,对于类类型new和delete还负责构造函数和析构函数的调用。
4.c++编译器对构造函数的调用
例如 Test t1(5);
Test t1 =5;//默认情况下,默认情况下,字面量5的类型为int,因此5无法直接用于初始化Test对象;但是编译器在默认情况下可以自动调用构造函数;
于是编译器尝试调用Test(int)生成一个临时对象,之后调用拷贝构造函数Test(constTest&)用临时对象对t2进行初始化。
上面是古代的编译器调用构造函数的过程。这种编译技术效率太低下了,现代的编译器进行了优化,
古代的编译器进行构造函数的调用方案为:
Test t1 = 5; Test t1 = Test(Test(5));
现代的编译器经过优化后,调用方案为
Test t1 = 5; Test t1(5);
这样直接就匹配了构造函数。
Test t2 = Test(5);//这句的调用也类似。
C++提供了explicit关键字用于阻止编译器对构造函数的调用尝试,剥夺了主动调用构造函数的权利,这时只能使用自动调用构造函数的方法。
例如explicitTset (int i)
{ ..}
这样之后,就不能主动调用构造函数,所以Test t1 = 5; 这句会编译不过去。
5.当多态遇见数组会发生什么
当对一个实现了多态的父类子类进行了下面的操作时,
Parent* p = NULL;
Child* c = NULL;
Child ca[3] = {Child(1, 2), Child(3, 4), Child(5, 6)};
p= ca;
c= ca;
cout<<hex<<p+1<<endl;
p->f();
c->f();
p++;这个地方执行完后就会出错,因为指针的操作时对指针类型操作的,它不会识别父类子类的。
c++;
p->f();
c->f();
不要将多态应用于数组,指针运算是通过指针的类型进行的,多态是通过虚函数表来实现的。
6.多重继承的毛病
不便于维护,容易产生二义性问题,任何多继承都可以通过单继承来替换。
在只有单继承的系统中,类之间的继承关系为一棵树,在引入多重继承的系统中,类之间的继承关系呈现为一张图。
C++中对多继承二义性的解决方案为:进行虚继承。
可以将共同基类设置为虚基类,这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝。
例如:class P1:virtual public Object
{
….
};
Class P2:virtualpublic Object
{
……..
};
7.C++实现java中接口的功能
C++中没有接口的概念,但是c++中可以使用纯虚函数实现接口
例如
class Interface
{
Public :
Virtualvoid func1()=0;
Virtualvoid func2(int i)=0;
Virtualvoid func3(int I,int j) =0;
}
接口类中只有函数原型的定义,没有任何数据的定义。
class Child : public Interface
{
Public:
void func1()
{……..}
void func2(int i)
{……..}
void func3(int I,int j)
{……..}
}
一个类,在某种意义上可以看作一个命名空间,一个命名空间,也可以看作一个类。
类内部定义的数据类型和全局空间里定义的数据类型是一样的。类模版里也能定义新的数据类型。
Sizeof是编译器的武器,在编译阶段就可以有答案。
8.写函数判断一个变量是否为指针
这个确实是比较难解决的,我是没想到,要解决这个问题,需要用到重载函数、函数模板、可变参数函数的内容,原理是利用函数重载的优先级问题。
template<typename T>
bool isPtr(T*)
{
return true;
}
bool isPtr(...)
{
return false;
}
使用这两个函数,
int* pi = NULL;
float* pf = NULL;
int i = 0;
cout<<isPtr(pi)<<endl;
cout<<isPtr(pf)<<endl;
cout<<isPtr(i)<<endl;
比这个更加高效的方法是
template<typename T>
char isPtr(T*);
int isPtr(...);
#define ISPTR(v) (sizeof(isPtr(v)) ==sizeof(char))///编译器直接就判断了,判断返回值的类型大小就行
int* pi = NULL;
float* pf = NULL;
int i = 0;
cout<<ISPTR(pi)<<endl;
cout<<ISPTR(pf)<<endl;
cout<<ISPTR(i)<<endl;