运算符重载对
1. 操作数的个数 2. 操作数出现的顺序都有确定要求
所以如果 对 << 这个操作符进行重载,
<<这个操作符有两个操作数,而且左右操作数有顺序要求
那么d1将成为this指针指向的对象,也将会 << 的左操作数
注意:下面的程序 是由于权限被放大而导致编译错误
首先调用构造函数,创建的d1实例的数据类型是 const Date
而d1.Print();这句话实际上执行的是 d1.Print(&d1);
此时但是实参传过去,将指针变量传给this指针这个形参的时候
由于this指针的数据类型是 Date*类型,所以权限被放大,将会导致编译错误
要想把this 指针改成const Date*类型的,
可以直接在类中定义这个成员函数时,写成void Print() const
void Print() const 中的const只针对this指针而提出
上述的权限问题只有针对指针型变量和引用型变量才讨论
因为只有这两种变量会存在访问权限的问题
const Date d1(2024, 1, 31);
d1.Print();
成员函数如果是一个对成员变量只进行读访问的函数,
建议在成员函数后面加const,这样const对象和非const对象都可以使用
C++中的六个默认成员函数:
1. 构造函数:作用是初始化对象
2. 析构函数:对象在销毁时会自动调用析构函数,完成对象中资源的清理工作
3. 拷贝构造:只有单个形参,该形参是对本类类型对象的引用(一般用const修饰)
在用已存在的类类型对象创建新对象时由编译器自动调用
4. 赋值(运算符)重载:把一个已有对象赋值给另一个对象
5. 取地址操作符重载:取出类的地址
6. const修饰的取地址操作符重载:取出类的地址,而且取出的地址是只读的
5,6这两个默认成员函数一般都不需要自定义,
直接采用编译器自动生成的默认成员函数即可
下面展示在编译器中这两个函数是怎么实现的
都是对 & 这个操作符进行重载,
5对应的是第一个操作符重载函数,直接对类取地址
6对应的是第二个操作符重载函数,其中this指针的权限被规定为const类型
初始化列表有两种代码写法
初始化列表是每个成员变量定义初始化的位置
写法一:初始化列表
这种初始化方式遵循几个原则
1. 根据成员变量的声明顺序来决定成员变量的初始化顺序
2. 可以在成员变量声明的时候给定缺省值,
但是一旦在列表中出现了这个成员变量的定义,将无视缺省值
3. 若成员变量满足下列条件,只能通过初始化列表进行初始化
3.1 成员变量被const修饰
3.2 成员变量是引用型变量
3.3 成员变量是自定义类型的成员
自定义类型的中的自定义成员必须初始化,初始化有两种方式
3.3.1 调用构造函数(编译器生成的 or 自己写的全缺省或无参的函数)
3.3.2 在初始化列表中直接给定初始值
4. 一个变量只能在初始化列表中出现一次
以下的构造函数被称为单参数构造函数
可以直接通过整型给这个函数初始化
在构造函数前加上explicit,就能让上述初始化时不能进行隐式转换
单参数构造函数支持隐式类型的转换
这是因为可以直接依靠 2 构造一个临时对象,
再通过拷贝构造创建cc2
如果在类中用static修饰了一个变量,
那么这个在创建该类的实例时需要注意:
1. 在类的内部不能既不能通过缺省值初始化这个变量
也不能在构造函数中的初始化列表中初始化
这是因为这个变量是属于所有创建出来的该类的实例
而不是属于某个实例
2. 在创建了两个该类的实例时,第二个实例中的n是在第一个实例中的n上修改的
3. 在创建了实例以后,由于访问限定符的限制,
无法直接通过实例访问n
4. 定义时是用 数据类型 变量所属类::被static修饰的变量 三段式进行初始化
但在外面无法通过 变量所属类::被static修饰的变量进行修改
只能通过调用类中的与该变量有关的成员函数进行修改
注意:一、3,4都是由于有private访问限定符进行限制
如果将这个变量不进行private限制,那么可以通过
1. A::n
2. 实例.n
3. A* p; p->n进行访问
二、如果这个变量进行private限制后还要进行访问,
那么可以通过重新写一个成员函数,放到public中,
这个成员函数的功能就只是为了拿到n
三、4中所说的 数据类型 变量所属类::被static修饰的变量
1. 这种形式的具体例子是 int A::n = 0;
而且n 必须是静态变量才能这么访问
2. 如果要在调用的时候使用这种形式调用成员函数
该成员函数也必须是静态函数才行
也就是先在A中写一个 static 成员函数(形参)
再用A::成员函数(实参)调用
静态的成员函数没有this指针
在将运算符 << 进行重载时,如果要把变量限定为private
又不能把重载函数直接写到类中(this指针的存在)
这就导致需要使用友元将这些变量专门提供给 再类外面的重载函数使用
B这个类受A类的类域的限制,这样定义的类指明了B是一个内部类
但是并不在A中占据存储空间
内部类就是外部类的友元类,也就是内部类可以直接访问外部类中的private限定的内容
先构造 + 拷贝构造 ->
优化成拷贝构造
A aa1 = 2;
下面这句话是通过 2 这个变量给A类的但操作数构造函数进行构造
构造生成一个临时变量,在给这个临时变量起个别名,叫做aa2
const A& aa2 = 2;
下面这个函数由于 f1 的形参直接是 A 这个类型
所以会发生拷贝构造
如果不想进行拷贝构造,可以采用两种方式
1. 将f1的形参改成引用形式,如f2()
2. 将将匿名对象作为实参传过去,如f1(A(2));
此时不会拷贝构造是因为
有些编译器会对匿名对象传参和拷贝对象这两步合二为一(即优化)
C/C++程序中程序内存区域划分为:
内核空间,栈,内存映射段,堆,数据段(也可以叫静态区),代码段(也可以叫常量区)
为什么要分这些区域?
为了方便管理
那个区域能够让程序员自己控制?
堆区,也就是动态开辟空间的区域(malloc,new)
全局变量、全局的静态变量、局部的静态变量都存在 数据段(静态区)中
局部变量,在函数内申请的静态数组中的数据(比如下面的num1数组)都存在栈区中
字符串变量,字符串本身是放到代码段(常量区中的),
但是下面定义的 接收字符串的字符串数组char2 是拷贝了在常量区中的字符串以后
复制到栈区中的,所以这个字符串数组char2是放到栈区的
如果堆char2进行解引用,得到字符a,仍放到栈区中
字符型指针pChar3虽然被const修饰,但是pChar3这个指针变量仍放在栈区中
如果堆pChar3进行解引用,将会得到字符串本身,所以*pChar3是放到常量区中的
注意:因为字符串本身和拷贝过来的字符数组放到不同的内存空间中,
所以可以对char2进行解引用修改字符数组中的值
但是不能对pChar3进行解引用修改常量字符串的值
ptr1是一个在函数test中的指针变量,该变量存储的地址的值是动态空间的开始的地址
但是ptr1作为一个指针变量仍放到栈区中,
对ptr1解引用得到的是申请的动态空间开始的地址,这个地址是是堆区中的地址