该博客用于记录自己在阅读过程中不懂的知识点,很少用到但比较重要的知识点以及模棱两可的知识点
一个类每个对象都有自己独立的存储空间存放各自的数据成员,然而成员函数的代码段在内存中只有一份,所以为了保证每个对象访问成员函数时针对的都是自己的数据成员,所有便有了this指针,每个成员函数都有一个隐式形参
T*const this(指向非常量版本的常量指针),它的值指向当前正在调用它的对象的起始地址,所以成员函数中对数据成员的操作前面都包含有一个this→,例:
class myclass{
public:
int returnI() {return i}
private:
int i;
}
myclass test; //此处忽略构造函数,后面再提
当使用test.returnI();时,其实会变成下面这样
myclass::returnI(&test);
函数内部会变成:
return this→i; //这几行代码是伪代码用于理解this的使用
this指针也是一个变量,所义说它必须遵守初始化规则,即不能将其绑定在一个常量对象上,所以常量对象不能调用普通的成员函数(调用函数会隐式的执行一个this指针的绑定,而这显然不符合初始化规则)。但事实上我们可以显式的将this指针指定为指向常量的常量指针,这样做必须保证这个成员函数不能改变调用它的对象的内容,当函数内容不会改变对象内容时,我建议这样做。
int returnI() const{return i} //参数列表后的const显式的指明该函数中的this是指向常量的常量指针,
当我们定义的某个成员函数类似与某个内置运算符时(+,-,*,/等),我们因该尽量模仿这个运算符,内置运算符将左侧的运算对象当成左值返回,例:
a+ b + c; //实际上是a+ b返回的对象再去+c
所以如果某个函数的行为类似内置运算符时,我们的返回值应该是一个左值引用,例:
myclass& add(const myclass &cl1){ //myclass的成员函数
i + = cl1.returnI();
return *this;
}
构造函数不能声明为const,当创建const对象时,该对象只有在构造完毕时才会获得const属性,这就意味着构造const对象的过程中可以向其写值
当类中存在构造函数时,编译器将不会生成默认构造函数
可以使用=default来定义默认构造函数
myclass()= default;
当定义的构造函数使用初始化列表初始化时,被初始化列表忽略的成员变量将会执行默认初始化
简单的情况下编译器将会为类自动的合成执行拷贝,赋值,销毁的操作,但某些情况下,自动合成的版本会无法正常工作(挖坑,,后面再提);
struct和class唯一的区别仅仅只是默认的访问权限不同,class是private,struct是public
类允许其他类或者函数访问其私有成员,方法是友元;
friend 函数声明
友元不是类成员,所以不受访问限定符的影响。
友元的声明并非通常意义上的函数声明,所以再进行一次普通的函数声明;
类中定义类型的别名一样有访问限制,且类型成员(类型别名成员)与普通成员不一样,它遵守先定义后使用,所以一般在开头定义;
定义在类内部的函数被自动定义为内联函数,即使在类内部声明为普通函数,也可以在类外定义的时候显式的设置为内联函数。
假如为们希望在一个const成员函数内依然可以改变某个数据成员,我们可以将其定义成:
mutable int number1;
就像函数一样,我们也能将类的定义和声明分开:
声明:class myclass;
这种声明被称为前向声明,在声明之后定义之前它是一种不完全类型,此时可以定义指向这种类型的指针或引用,也可以声明(但不能定义)以其为参数类型或者返回值类型的函数,此时编译器只是知道了有该种类型存在,而并不知道它的内容是怎样,到底需要多大的内存空间,由此可以知道,我们并不能在一个类中建立它自己的对象,但是可以建立指向它的指针;
类域内定义的友元函数调用时必须具有该类类型的实参,因为当调用一个友元函数时,名字搜索域也包含与实参关联的那些名称空间和类中的名字
友元函数就算定义在类内部也必须在外面声明
一般来说内层作用域可以重新定义外层作用域中的名字,但是在类中,改名字如果代表一种类型,则不能重新定义
如果没有在构造函数的初始化列表中初始化数据成员,则这些初始化将会在函数体之前执行默认初始化,换句话说,你的数据成员将会执行两次赋值,第一次默认初始化,第二次你的代码显式赋值,如果该数据成员是const(const一旦被初始化后便不能再赋值)或者引用(引用不能二次绑定)的话,则会发生错误,同样的,当成员属于某种没有定义默认构造函数的对象时也会发生错误。
构造函数初始化列表的执行顺序与数据成员的定义顺序相同,如果一个成员的初始化由另外一个成员作为右值的话,顺序就很重要了
委托构造函数,顾名思义,就是某个构造函数初始化时调用另外一个构造函数来帮他初始化,例:
myclass(intnum1): i(num1) {}
myclass(intnum2): myclass(num2) {}
类中的所有成员必须初始化,意味着不能默认初始化的数据成员(如类类型)必须手动初始化
当类的构造函数只需要一个实参时,可以使用隐式的类型转换将实参类型转换为类类型,例:
class myclass{
public:
myclass(intnum1, int num2 = 2): i1(num1), i2(num2){}
int testclass(const myclass & test){
return test.i1 + test.i2;
}
private:
int i1;
int i2;
};
int main()
{
myclass class1(2);
cout << class1.testclass(1) << endl; //调用testclass函数时理应传入一个myclass类型的实参,但为什么为传的是
//一个整型的1,却还是成功了呢?因为myclass的某个构造函数只需要一个实参,
//此时我们传入1的时候它隐式的执行了一个构造函数,构造了一个临时的对象,
//此时应注意,虽然构造函数有两个形参,但有一个有默认实参,所以是一个只需要
//一个实参的构造函数
return 0;
}
还有一点必须提,编译器只会自动执行一步类型转换,也就是说假如形参需要的是string类型的,我们传入一个字面值是不行的,因为它会先转换成string类型,再转换成类类型
如果我们不希望编译器执行隐式的类型转换,则可以在构造函数前面加上explicit关键字,只需要在声明时加
在上面的例子中:
myclass class1(2);
也可以写成:
myclass class1 = 2; //拷贝的形式
但如果加了explicit关键字则不能以拷贝的形式写,尽管加上explicit关键字后不能隐式的转换,但我们可以使static_cast
执行显式的转换
聚合类的特性:
所有成员都是public的
没有定义任何构造函数
没有类内初始值
没有基类,也没要virtual构造函数(后面讲)
允许使用花括号扩起来的初始值列表初始化聚合类的数据成员
字面值常量类是聚合类或者满足下列要求的类
数据成员都是字面值类型
含有constexpr构造函数(普通的构造函数不能是吃哦是const的)
如果含有类内初始值,则必须是常量表达式,或者成员是类类型的,则必须使用成员自己的constexpr构造函数
类的析构函数必须使用默认的
constexpr构造函数可以声明成=default或者删除函数的形式(挖坑后面填)
静态成员函数属于类,不与任何对象绑定,所以它们不包含this指针,且不能声明为const。
静态成员函数定义时不用重复static关键字,只需要在声明时加上就行了。
因为静态数据成员不属于任何对象,所有构造函数并不能初始化它,应该在类外定义和初始化静态成员,且只能定义
一次,且应该是全局的
其实我们也能在类内定义静态数据成员,但必须保证该成员是字面值常量类型的constexpr,初始值必须为常量表达式
静态数据成员可以是不完全类型,它的类型就是该类的类型(因为类定义对象时需要分配内存,所以普通数据成员如果使用该类类型就不知道该分配多少内存空间,而静态数据成员不属于任何类,所有不存在定义时分配内存的问题),静态数据成员还可以作为默认实参,而这些都是普通数据成员不能做的地方