C++知识点更多,文章更用来查缺补漏更好,希望对你们有帮助


1.类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。

但是类里面并不是什么都没有,实际上,在什么都不写的情况下,编译器会产生6个默认成员函数

默认成员函数:默认成员函数可以是自己创建的,也可以是编译器默认生成的成员函数

举例(以 构造函数 为例)

class A{ A()//不带参数 { ; } A(int x = 4)//全缺省参数 { ; }};

注意事项:

类在实例化的时候一定会调用默认成员函数

2. 构造函数

a. 概念

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用(即实例化的时候会调用),以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次

b. 特性

1. 函数名与类名相同。

2. 无返回值。 (void 也不用写)

3. 对象实例化时编译器自动调用对应的构造函数。

4. 构造函数可以重载。

代码举例

#includeusing namespace std;class Data{public:Data(int year = 2, int month = 2, int day = 2){_year = year;_month = month;_day = day;cout << _year << "年" << _month << "月" << _day << "日" << endl;}private:int _year;int _month;int _day;};int main(){Data t;return 0;}

运行结果:

分析:

我们可以看到在实例化的时候确实调用了默认成员函数

疑问:

为什么不可以写成 Data t() ” />因为这样编译器无法辨别它是构造函数还是一个函数的声明(返回类型:Data ,函数名:t , 参数:无参)

#includeusing namespace std;class Data{public:Data(int year = 2, int month = 2, int day = 2){_year = year;_month = month;_day = day;cout << _year << "年" << _month << "月" << _day << "日" << endl;}Data(){;}private:int _year;int _month;int _day;};int main(){Data t;return 0;}

这两个成员函数不构成重载函数,编译器无法确定初始化的时候调用哪个函数

#includeusing namespace std;class Data{public:Data(int year = 2, int month = 2, int day = 2){_year = year;_month = month;_day = day;cout << _year << "年" << _month << "月" << _day << "日" << endl;}private:int _year;int _month;int _day;};int main(){Data t(2024,1,5);return 0;}

运行结果:

c. 默认构造函数

默认构造函数:

  • 默认构造函数可以是自己创建的(不带参数,或者是 全缺省参数),也可以是编译器默认生成的成员函数
  • 默认构造函数只能有一个

如果类中没有构造函数,则C++编译器会自动生成一个无参的默认构造函数

对编译器生成的默认构造函数处理的猜测

编译器生成的默认构造函数对内置类型(如 int , char ,int * … …)不做处理(即得到的还是随机值), 对自定类型(如 calss, union ,struct ……)会自动调用它的构造函数

代码举例

#includeusing namespace std;class Data{public:void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}private:int _year;int _month;int _day;};int main(){Data t;t.Print();return 0;}

运行结果:

这里可以看出对 内置类型 没有做任何处理

#includeusing namespace std;class Data{public:Data(int year = 0, int month = 0, int day = 0){_year = year;_month = month;_day = day;cout << _year << "年" << _month << "月" << _day << "日" << endl;}private:int _year;int _month;int _day;};class A{Data t1;Data t2;};int main(){A t3;return 0;}

运行结果:

很明显:实例化对象 t1 , t2 的时候,同时也调用了 它的默认构造函数

注意事项:

C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值

代码举例

#includeusing namespace std;class Data{public:Data(int year = 0, int month = 0, int day = 0){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;};class A{public:Data t1;Data t2;int size = 0;};int main(){A t3;cout << t3.size << endl;return 0;}

运行结果:

3. 析构函数

a. 概念

析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

b. 特性

1. 析构函数名是在类名前加上字符 ~。

2. 无参数无返回值类型。 (void 也不用写)

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数

代码举例

using namespace std;class stack{public:stack(int n = 4){int* pa = (int*)malloc(sizeof(int) * n);if (pa == NULL){perror("malloc fail");return;}_a = pa;_top = 0;_capacity = 4;cout << "已开辟4字节空间大小" << endl;}~stack(){free(_a);_a = NULL;cout << "数组已经销毁了" << endl;}private:int* _a;int _top;int _capacity;};int main(){stack st;return 0;}

运行结果:

c. 默认析构函数

  • 默认析构函数只能有一个

对编译器生成的默认析构函数处理的猜测

编译器生成的默认析构函数对内置类型(如 int , char ,int * … …)不做处理, 对自定义类型(如 class, union ,struct ……)会自动调用它的析构函数

代码举例

#includeusing namespace std;class stack{public:stack(int n = 4){int* pa = (int*)malloc(sizeof(int) * n);if (pa == NULL){perror("malloc fail");return;}_a = pa;_top = 0;_capacity = 4;cout << "已开辟4字节空间大小" << endl;}~stack(){free(_a);_a = NULL;cout << "数组已经销毁了" << endl;}private:int* _a;int _top;int _capacity;};classST{stack t1;stack t2;};int main(){ST t3;return 0;}

运行结果:

注意事项:

  1. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数;有资源申请时,一定要写,否则会造成资源泄漏(比如 类中有动态开辟的的空间时,要写一个析构函数及时释放空间)
  2. 析构函数像栈一样,都是先进后出释放空间

举例

#includeusing namespace std;class stack{public:~stack(){free(a);a = nullptr;}private:int* a = (int*)malloc(sizeof(int) * 4);};int main(){stack t1;stack t2;return 0;}

销毁时,先调用的是后面的析构函数(先t2 , 再t1)

4. 拷贝构造函数

a. 概念

拷贝构造函数只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰,是为了防止改变形参的值),在用已存在的类类型对象创建新对象时由编译器自动调用

举例

#include using namespace std;class Data{public:Data(int year = 0,int month = 0,int day = 0){_year = year;_month = month;_day = day;}Data(Data& d){_year = d._year;_month = d._month;_day = d._day;}private:int _year;int _month;int _day;};int main(){Data t1;Data t2(t1);return 0;}

b. 特性

1. 拷贝构造函数是构造函数的一个重载形式

2. 拷贝构造函数的参数只有一个必须是类类型对象的引用,(使用传值方式编译器直接报错,因为会引发无穷递归调用)

注意事项:

传值传参时,如果参数是内置类型,如果直接拷贝给形参,但是如果是自定义类型,需要先调用形参的拷贝构造函数(编译器无法承担直接拷贝自定义类型变量的后果)

原因:

对于栈,如果直接拷贝,就会是这样:

如果调用默认析构函数,同一块空间就会被释放两次,所以直接拷贝自定义函数是行不通的

分析(对于参数不是类对象的引用 错误在哪)

class Data

{

pubilc:

Data(int year = 0 , int month = 0, int day = 0)

{

_year = year;

_month = month;

_day = day;

}

Data(Data d)

{

_year = d.year;

_month= d.month;

_day = d.day;

}

private:

int _year;

int _month;

int _day;

}

int mian()

{

Data d1;

Data d2(d1);

return 0;
}

c. 对编译器生成的默认拷贝构造函数处理的猜测

如果没有自己写的拷贝函数,调用拷贝构造函数调用的是 编译器生成的默认拷贝构造函数 (对传值会浅拷贝)

代码举例

#include using namespace std;class Data{public:Data(int year = 0,int month = 0,int day = 0){_year = year;_month = month;_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;};int main(){Data t1(2023,1,8);Data t2(t1);t2.Print();return 0;}

运行结果:

调用的是编译器自己生成的默认拷贝构造函数(即浅拷贝)

但某些情况下,我们需要自己去写拷贝构造函数,完成深拷贝(如:写栈的时候,不能浅拷贝,因为 定义的 int *a 如果是浅拷贝,则类定义的两个对象的成员变量a都是指向同一块空间)

5. 赋值运算符重载

a. 运算符重载

运算符重载是具有特殊函数名的函数 ,可以帮助实现自定义类型的大小比较

函数名字为:关键字operator后面接需要重载的运算符符号。(如:operator+)
注意事项:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变(如:内置类型的 + 不能改变它原有的含义)
  • 作为类成员函数重载时,其形参看起来比操作数数目少1(因为成员函数的第一个参数为隐藏的this)
  • .* :: sizeof ” />

    代码举例

     #include using namespace std;class Data{public:Data(int year = 0,int month = 0,int day = 0){_year = year;_month = month;_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day <(Data& d){return _year > d._year || ((_year == d._year) && (_month > d._month)) ||((_year == d._year) && (_month == d._month) && _day > d._day);}bool operator >=(Data& d){return *this == d || *this > d;}bool operator = d);}bool operator <=(Data& d){return *this == d || *this < d;}private:int _year;int _month;int _day;};int main(){Data t1(2023,1,8);Data t2(2023,1,8);if (t1 == t2){printf("相等\n");}return 0;}

    运行结果:

    b. 赋值运算符重载

    将一个类定义的对象的值赋给另一个对象

    代码举例

    #include using namespace std;class Data{public:Data(int year = 0,int month = 0,int day = 0){_year = year;_month = month;_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}Data& operator=(const Data& d){_year = d._year;_month = d._month;_day = d._day;return *this;}private:int _year;int _month;int _day;};int main(){Data t1(2023,1,8);Data t2(2023,1,9);Data t3;t3 = t1 = t2;t1.Print();t3.Print();return 0;}

    运行结果:

    分析:

    为了能够连续赋值(参考内置类型的连续赋值:int i = j = k ,从右往左运行,把 k 赋值给了 j ,返回 j 的值,再把 j 赋值给 i ,返回 i 的值)

    注意事项:

    • 参数类型:const T&(传递引用可以提高传参效率)
    • 返回值类型:T& (返回引用可以提高返回的效率,有返回值的是为了支持连续赋值)
    • 检测是否自己给自己赋值
    • 返回 *this :要复合连续赋值的含义
    • 赋值运算符重载成类的成员函数第一个参数是 this指针 ,重载成全局函数 没有 this 指针

    对编译器生成的默认赋值运算符重载处理的猜测

    和编译器默认生成的拷贝构造函数一样,都是浅拷贝(逐字节的拷贝)

    代码举例

    #include using namespace std;struct Data{public:Data(int year = 1, int month = 1, int day = 1){ _year = year; _month = month; _day = day;}void Print(){ cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;};int main(){Data t1(2024, 1, 12);Data t3;t3 = t1;t3.Print();return 0;}

    运行结果:

    c. 前置++ 和 后置++ 重载

    前置– 和 后置–实现类似

    代码举例

    #include #include using namespace std;struct Data{public:Data(int year = 1, int month = 1, int day = 1){ if (year > 0 && month > 0 && month  0 && day <= GetMonthDay(year, month)) { _year = year; _month = month; _day = day; }else { cout << "日期错误" < GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); _month++; if (_month == 13) {_year++;_month = 1; } }return *this;} int GetMonthDay(int year, int month){ int MonthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if (((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0){MonthDay[2]++;} return MonthDay[month];}Data& operator++(){ *this += 1; return *this;}Data operator++(int){ Data tmp = *this; *this += 1; return tmp;}private:int _year;int _month;int _day;};Data& Data ::operator++(){*this += 1;return *this;}Data Data :: operator++(int) {Data tmp = *this;*this += 1;return tmp;}int main(){Data t1(2024,2 ,23);t1++; //调用 t1.operator++()++t1;//调用 t1.operator++(0)参数也可能是其它 int 类型的数字return 0;}

    分析:

    为了区分前置加加和后置加加,且能构成重载函数,参数一定要有一个 int 类型(无实际意义)

    6. const 成员函数

    将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改

    代码举例

    struct A{public:void fun() const{_a = 5;}private:int _a = 4;};int main(){A aa;aa.fun();return 0;}

    分析:

    // 相当于 const A * this

    所以 this 的成员的值不可以修改

    7.取地址及const取地址操作符重载

    这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

    代码举例

    #include using namespace std;struct A{private:int _a = 4;};int main(){A aa;cout << &aa << endl;return 0;}
    include using namespace std;struct A{A* operator&(){return this;}const A* operator&() const{return this;}private:int _a = 4;};int main(){A aa;cout << &aa << endl;return 0;}