> 技术文档 > 【C++】零基础教程,看这一篇就够了

【C++】零基础教程,看这一篇就够了

目录

一、C++简介

1、学习各个点的重要级别介绍

2、为什么要学习C++?

3、  C++语言的发展史(了解)

4、  C++特点(了解)

5、  面向过程与面向对象区别(熟悉)

6、  面向对象编程的重要知识点(熟悉)

8、  开发环境(掌握)

二、从C到C++

1、  引用(掌握)

1.1 概念

1.2 引用的性质

1.3 引用的参数

【面试题】

 核心区别

2、  赋值(熟悉)

3、  键盘输入(熟悉)

4、  string字符串类(掌握)

5、  函数

5.1 内联函数(熟悉)

5.2 函数重载(重点)

5.3 哑元函数(熟悉)

三、面向对象基础

1、类与对象(重点)

1.1 概念

1.2 类的内容

1.3 对象的创建

【面试题】

 核心区别

【练习】1、键盘输入一个100-999之间的数,依次输出这个数的个十百位。

2、封装(重点)

3、构造函数(掌握)

3.1 基本使用

3.2 构造初始化列表

3.3 隐式调用和显式调用

3.4 拷贝构造函数

3.4.1 概念

【思考】默认的拷贝构造函数是否存在隐患?

3.4.2 浅拷贝

3.4.3 深拷贝

【思考】当前深拷贝的代码是否存在隐患?

4、  析构函数(析构函数)

5、作用域限定符::

5.1 名字空间(熟悉)

5.2 类内声明,类外定义(掌握)

6、this指针(掌握)

6.1 概念

6.2 功能

6.2.1 类内调用成员

6.2.2 区分重名的成员变量和局部变量

6.2.3 链式调用

未完待续,后面内容继续从此处更新......


一、C++简介

1、学习各个点的重要级别介绍

重点一般分为一下几个级别:

● 重点:面试考试大概率涉及,需要不借助任何的资料掌握。

● 掌握:面试考试可能涉及,需要不借助任何资料掌握。

● 熟悉:面试考试可能涉及,可以稍微参考资料掌握。

● 了解:面试考试小概率涉及,能吹吹就行。

2、为什么要学习C++?

1、  C++是一个新的就业方向。

2、  C++可以做什么?

● 服务器开发:游戏服务器、推荐服务器等等。当然还包括应用程序开发、嵌入式软件开发。

● AI人工智能

● QT,应用程序开发

3、  拓展知识面

● 之前学习的C语言,是面向过程的编程语言。C++面向对象编程思想的一门语言。

3、  C++语言的发展史(了解)

       1983年,贝尔实验室(Bell Labs)的Bjarne Stroustrup发明了C++。 C++在C语言的基础上进行了扩充和完善,是一种面向对象程序设计(OOP)语言。

       Stroustrup说:“这个名字象征着源自于C语言变化的自然演进”。还处于发展完善阶段时被称为“new C”,之后被称为“C with Class”。C++被视为C语言的上层结构,1983年Rick Mascitti建议使用C++这个名字,就是源于C语言中的“++”操作符(变量自增)。而且在共同的命名约定中,使用“+”以表示增强的程序。

       常用于系统开发,引擎开发、嵌入式开发等应用领域, 至今仍然是最受广大程序员喜爱的编程语言之一。

4、  C++特点(了解)

● 在支持C基础上,全面支持面向对象编程

● 编程领域广泛,功能强大(最难的编程语言之一)

● C++语言的标准还一直保持着更新,本次学习主要以ISO C++98与11 标准为主。

● 为数不多的支持底层操作的面向对象语言。

● 再面向对象语言中运行效率极高。

5、  面向过程与面向对象区别(熟悉)

【思考】如果把大象装进冰箱,应该怎么做?

1、  (我)打开冰箱

2、  (我)把大象放进去

3、  (我)把冰箱门关上

       上面这种方式就是典型的面向过程的编程思想,这种思想关注的重点是“过程”,“过程”指的是一系列有序的步骤,只要按照这个步骤来做,就可以得到预计的结果。这种程序的特点执行效率极高(因为都是亲历亲为)。适合小体量的软件项目编程,偏性能的项目一般这样做。

使用面向对象的思想把大象装进冰箱:

1、  (我)把大象和冰箱拟人化

2、  (我)给大象和冰箱安排任务

3、  大象和冰箱执行任务

       面向对象的语言,关注的重点是“对象”,再计算机中,“对象”可以理解为一系列由于某种原因聚焦再一起的数据,再编程过程中处理对象之间的关系。这种思考方式更近于人类的思考方式。这种程序也有缺点,缺点是运行效率低,但是编程效率高,适合大规模的软件项目

总结:

6、  面向对象编程的重要知识点(熟悉)

● 

● 对象

● 封装

● 继承

● 多态

封装 -> 继承 -> 多态。也被称为面向对象的三大特性。

8、  开发环境(掌握)

      单论C++的开发环境,没有严格的要求,为了学习的方便,直接使用下门课程的环境进行开发。即Qt Creator。

只需要再一个不包含任何中文路径下,一直点击下一步即可。

安装完成后,为了使其支持中文输出,更改下面的编码:

设置完成后,重新启动QtCreator,就可以新建一个C++项目了,操作步骤如下:

1、  点击

2、  再弹出的窗口中,按照下图所示进行操作

3、  在弹出的窗口中,先输入项目名称,再设定项目路径,最后点击下一步。

需要注意,中途不得出现任何的中文字符,和特殊字符

4、  再弹出的窗口中,直接点击“下一步”

5、  在项目管理界面直接点击完成。

6、  可以看到新项目已经创建成功了

.pro文件为项目配置文件,通常不需要手动进行编辑,只有在项目中完全开启C++11功能时,增加下面这句话即可。

QMAKE_CXXFLAGS += -std=c++11

添加完成后,忘记保存。

.cpp文件为C++的源文件,用于编写C++代码。

点击左下角的(快捷键ctrl+R),即可运行项目。

补充几个快捷键的使用技巧。

1、  Alt + 0 显示/隐藏边栏。

2、  ctrl+A全选,再ctrl+i对齐。 代码排版

3、  ctrl+F 搜索+替换

更改主题

二、从C到C++

本章介绍一些C++拓展的非面向对象的功能。

1、  引用(掌握)

1.1 概念

       引用从一定程度上讲是指针的平替。几乎被所有的面向对象编程语言所使用,引用相当于对某一目标变量起“别名”。

操作引用与操作原变量完全一样。

#include using namespace std;int main(){ int a = 1; // b是a的引用 int &b = a; cout << a << \" \" << &a << endl; // 1 0x61fe88 cout << b << \" \" << &b << endl; // 1 0x61fe88 return 0;}

1.2 引用的性质

● 可以改变引用的值,但是不能再次成为其他变量的引用

#include using namespace std;int main(){ int a = 1; // b是a的引用 int &b = a; int c = 3; b = c; // 赋值,不是引用,b还是a的引用 b++; cout << a << \" \" << &a << endl; // 4 0x61fe88 cout << b << \" \" << &b << endl; // 4 0x61fe88 cout << c << \" \" << &c << endl; // 3 0x61fe84 return 0;}

● 声明引用时,必须要初始化

#include using namespace std;int main(){ int a = 1;// int &b; // 错误,引用必须初始化 b = a; return 0;}

● 声明引用时(普通使用),不能初始化为NULL。

#include using namespace std;int main(){ int a = 1;// int &b = NULL; // 错误,常量不能直接起别名 return 0;}

● 声明引用的时候,初始化的值可以是纯数值,但是此时要用const关键字修饰引用,表示该引用为常量引用,这样的引用的值不可变

#include using namespace std;int main(){ const int &b = 12; // 常量引用// b = 444; // 错误 常量引用的数值不能被改变的。 return 0;}

● 可以将变量引用的地址,赋值给一个指针,此时指针指向的还是原来的变量。

#include using namespace std;int main(){ int a = 1; int &b = a; int *c = &b; // C同时指向了a和b cout << a << \" \" << &a << endl; // 1 0x61fe84 cout << b << \" \" << &b << endl; // 1 0x61fe84 cout << *c << \" \" << c << endl; // 1 0x61fe84 return 0;}

● 可以对指针建立引用

#include using namespace std;int main(){ int a = 1; int &b = a; int *c = &b; int *&d = c; // d是c的引用 cout << a << \" \" << &a << endl; // 1 0x61fe84 cout << b << \" \" << &b << endl; // 1 0x61fe84 cout << *c << \" \" << c << endl; // 1 0x61fe84 cout << *d << \" \" << d << endl; // 1 0x61fe84 return 0;}

● 可以使用const修饰引用,此时如果原变量的值改变,引用的值也会改变。

#include using namespace std;int main(){ int a = 2; const int &b = a;// b++; 错误 b是只读的 a++; cout << a << \" \" << &a << endl; // 3 cout << b << \" \" << &b << endl; // 3 return 0;}

1.3 引用的参数

【思考】

写一个函数,函数有两个参数a和b,函数的功能是交换两个传入的参数原来变量的值。

#include using namespace std;// 法一:操作拷贝的是副本,不符合需求void test(int a,int b){ int temp = a; a = b; b = temp; cout << a << endl; cout << b << endl;}// 法二:C语言写法,在C++中不推荐这种写法void test1(int *a,int *b){ *a = *a ^ *b; *b = *a ^ *b; *a = *a ^ *b;}// 法三:C++编程方式,符合需求void test2(int &a,int &b) //引用作为参数传递{ a = a ^ b; b = a ^ b; a = a ^ b;}int main(){ int a1 = 1; int b1 = 2; test2(a1,b1); cout << a1 << endl; cout << b1 << endl; return 0;}

       引用作为参数进行定义

       好处:在参数传递的时候不会产生副本,这样会提高程序的运行效率。我们在正常编译中,建议使用引用传递参数。

       引用形参,在不参与计算的情况下,我们建议使用const进行修饰,以达到引用的安全性。

#include using namespace std;void test2(const int &a){// a++; // 错误 const修饰,无法修改 cout << a << endl; //1}int main(){ int a1 = 1; test2(a1); cout << a1 << endl; //1 return 0;}

【面试题】

 指针和引用的区别?

指针:是一个变量,存储另一个变量的内存地址。
需要显式解引用(*)来访问目标值。

引用:是变量的别名,本质是绑定到目标变量的“另一个名字”。
无需解引用,直接操作引用即操作原变量。

 核心区别

特性 指针 引用 初始化要求 可以声明时不初始化(但建议初始化) 必须初始化,且不能重新绑定到其他变量 空值(Null) 可以赋值为 nullptr 不能为空,必须绑定有效对象 重定向能力 可以指向不同对象 一旦初始化,无法更改绑定对象 内存占用 占用独立内存(存储地址) 不占用独立内存(仅是别名) 多级间接访问 支持多级指针(如 int**) 不支持多级引用,但支持对指针的引用(如 int*&运算操作 支持指针算术(+-++ 等) 不支持运算,操作等同于原变量

2、  赋值(熟悉)

通常使用=号进行赋值操作,C++新增了以下赋值的语法

#include using namespace std;int main(){ int a(1); // 等同于 int a = 1; cout << a << endl; int b(a); // 等同于 int b = a; cout << b << endl; int c(a+b); // 等同于 int c = a + b; cout << c << endl; return 0;}

C++11中对上述写法进行升级

#include using namespace std;int main(){ int a(1); // 等同于 int a = 1; cout << a << endl; double b = 3.14; int b1 = b; cout << b1 << endl; // 3 int b2(b); cout << b2 << endl; // 3 int b3{b}; // 升级:对数据窄化提出警告 cout << b3 << endl; // 3 return 0;}

3、  键盘输入(熟悉)

可以使用cin把用户在命令行中输入的内容赋值到变量中。

cin与cout一样,都是数据头文件iostream中的标准输入输出流。

#include using namespace std;int main(){ int a; // C++的字符串是string string str; cout << \"请输入一个数字和字符串\" <> a >> str; // 接收键盘输入,一个整数和一个字符串,可以连续操作 cout << a << str << endl; return 0;}

如果cin输入的字符串包含空格,可以使用下面的方式:

#include using namespace std;int main(){ // C++的字符串是string string a; cout << \"请输入一个字符串,可以包含空格\" << endl; getline(cin,a); cout << a << endl; return 0;}

4、  string字符串类(掌握)

      string不是C++的基本数据类型,它是一个C++标准库中的字符串类,使用时需要引入头文件#include 。而不是string.h

       string在绝大多数情况下可以代替C语言的字符串,不必担心内存是否足够和字符串长度等,其中内部还包含了很多的字符串处理函数,可以完成各种情况下的字符串的处理功能。

       string和C语言相同,字符串编码使用的是ASCII码。不支持中文处理。

#include using namespace std;int main(){ string str = \"helloworld\"; cout << str.size() << endl; // 10 cout << str.length() << endl; // 10 cout << str[5] << endl; // w cout << str.at(5) << endl; // w return 0;}

   两种方式都可以,但是在C++中更推荐使用at函数,原因是at函数更安全[ ]的执行效率高

#include using namespace std;int main(){ string str = \"helloworld\"; cout << str[100] << endl; // 会输出一个越界数据 cout << str.at(100) << endl; // 程序运行停止 cout << \"hello\" << endl; return 0;}

string类支持多种遍历方式:

● 普通循环(以for循环为主)

● C++11: for each循环

#include using namespace std;int main(){ string str = \"helloworld\"; // 以for循环的方式进行输出字符串 for(int i = 0; i < str.size(); i++) { cout << str.at(i); } cout << endl; // 以for each的方式循环遍历字符串 for(char i : str) { cout << i; } return 0;}

了解:字符串与数字的转换

#include #include  // 字符串流using namespace std;int main(){ string s = \"123\";// int i = s; 错误 // string → int istringstream iss(s); int i; iss >> i; cout << i << endl;// int → string// string s2 = i; 错误 stringstream ss; ss << i; string s2 = ss.str(); cout << s2 << endl; return 0;}

5、  函数

5.1 内联函数(熟悉)

      内联函数用于取代C语言中宏定义的函数,内联函数的正确使用可以提升程序的执行效率

     内联函数在编译的时候,直接把函数体展开到主函数中编译,在运行期间可以减少调用的开销,以空间换取时间。

通常将具有以下性质的函数写为内联函数

● 代码长度5行以内

● 不包含复杂的控制语句

● 频繁的被调用

内联函数的关键字inline

后续学习的成员函数默认添加inline关键字修饰

但是我们手动添加上inline关键字,目的是将函数声明成内联函数,但是这个不是我们能决定的,编译器有自己的判断准则,我们只是给编译器提一个建议,具体是否变为内联函数,还是编译器自己决定的。

include using namespace std;// 内联函数inline void print_string(string str){ cout << str << endl;}int main(){ print_string(\"helloworld\"); return 0;}

5.2 函数重载(重点)

       C++中允许多个函数使用同一个名称,这种用法就是函数重载,函数重载要求函数名称相同,但是参数不同(类型不同、数量不同)或者前后顺序不同,与返回值类型无关。

#include using namespace std;void print_show(int i){ cout << \"调用了int重载:\" << i << endl;}void print_show(float i){ cout << \"调用了float重载:\" << i << endl;}void print_show(string i){ cout << \"调用了string重载:\" << i << endl;}// 错误 名称相同,参数类型相同,编译器无法区分// 与返回值类型无关//int print_show(int s)//{// cout << \"调用了int2重载:\" << s << endl;// return s;//}// 错误 const关键字,也无法作为判断依据//void print_show(const string s)//{// cout << \"调用了string重载:\" << s << endl;//}int main(){ print_show(1.2); return 0;}

5.3 哑元函数(熟悉)

函数的参数只有类型,没有名称,有这样参数的函数就是哑元函数。

作用1:哑元函数可以用来区分函数重载

作用2:运算符重载中会用到

#include using namespace std;// 哑元函数void print_show(int){ cout << \"调用了int重载\" << endl;}int main(){ print_show(1); return 0;}

三、面向对象基础

1、类与对象(重点)

1.1 概念

:类是一个抽象的概念,用于描述同一类对象的特点

对象根据类的概念所创造的实体

【思考】:一个对象可以没有对应的类嘛?

                  不可以的。

对于现在的学习,如果只写一个类,没有对应的对象,这样的类是没有意义的。

1.2 类的内容

类中最基础的内容包括两个部分:一个是属性,一个是行为

● 属性:表示一些特征项的数值,比如说:身高、体重、年龄、性别、型号、而这些特正项的数值也被称为”成员变量“。属性一般以名词存在。

 行为:表示能执行的动作,能干什么事?比如说:吃饭、睡觉、打架、叫、唱、跳、rap、打篮球。行为一般通过函数来表示,也被称为”成员函数“。行为一般以动词存在。

成员 = 成员函数 + 成员变量。

【例子】以手机为例,来说明类的定义。

规定手机能够播放音乐、运行游戏、打电话。手机有:品牌、型号、重量等等。

#include using namespace std;// 大驼峰命名法(帕斯卡命名法)// 每个单词的首字母要大写class MobilePhone{public: // 权限:public最开放的权限 string brand; // 品牌 string model; // 型号 int weight; // 重量 void play_music() { cout << \"来财\" << endl; } void run_game() { cout << \"无畏契约、第五人格、cs\"; } void call() { cout << \"您拨打的电话正在通话中,请稍后在拨\" << endl; }};int main(){ return 0;}

1.3 对象的创建

C++中存在两种类型的对象。

● 栈内存对象

对象所在的{}执行结束后,自动被销毁

#include using namespace std;// 大驼峰命名法(帕斯卡命名法)// 每个单词的首字母要大写class MobilePhone{public: // 权限:public最开放的权限 string brand; // 品牌 string model; // 型号 int weight; // 重量 void play_music() { cout << \"来财\" << endl; } void run_game() { cout << \"无畏契约、第五人格、cs\"; } void call() { cout << \"您拨打的电话正在通话中,请稍后在拨\" << endl; }};int main(){ MobilePhone mp; // 创建一个栈内存对象 mp.brand = \"华为\"; mp.model = \"遥遥领先\"; mp.weight = 500; cout << mp.brand << \" \" << mp.model << \" \" << mp.weight << endl; mp.play_music(); mp.run_game(); mp.call(); return 0;}

● 堆内存对象

      必须使用new关键字创建,使用指针保存,如果不使用delete关键字销毁,则堆内存对象会持续存在,而导致内存泄漏。堆内存调用对象内容时,使用 ->而不是\" . \"

#include using namespace std;// 大驼峰命名法(帕斯卡命名法)// 每个单词的首字母要大写class MobilePhone{public: // 权限:public最开放的权限 string brand; // 品牌 string model; // 型号 int weight; // 重量 void play_music() { cout << \"来财\" << endl; } void run_game() { cout << \"无畏契约、第五人格、cs\"; } void call() { cout << \"您拨打的电话正在通话中,请稍后在拨\" <brand = \"小米\"; mp->model = \"mi6\"; mp->weight = 200; cout <brand << \" \" <model << \" \" <weight <play_music(); mp->run_game(); mp->call(); delete mp; // 手动销毁 mp = NULL; // 建议指向空,防止后面误用 return 0;}

【面试题】

malloc和free与new和delete 的区别?

malloc返回值为 void *类型,需要强制转换,而new创建什么类型对象,返回什么类型地址。

 核心区别

特性 malloc/free new/delete 语言 C/C++ C++ 特有 内存分配方式 仅分配/释放原始内存 分配内存并调用构造/析构函数 类型安全 需手动类型转换 自动类型推导 内存大小计算 需手动指定字节数 自动计算类型大小 失败处理 返回 NULL 抛出 std::bad_alloc 异常(默认) 重载支持 不支持 支持运算符重载 数组处理 需手动计算空间 支持 new[]/delete[] 语法 与对象生命周期的结合 无关 管理对象构造与析构

【练习】1、键盘输入一个100-999之间的数,依次输出这个数的个百位。

参考代码:

int main(){ //1、键盘输入一个100-999之间的数,依次输出这个数的个十百位。 int a; cout << \"请输入一个100-999之间的数:\" <> a; cout << \"个位:\" << a%10 << endl; cout << \"十位:\" << a/10%10 << endl; cout << \"百位:\" << a/100 << endl; return 0;}

【练习】2输入一行字符串,分别统计出其中的英文字母,数字和其他字符的个数。

参考代码:

 int main(){ //2、输入一行字符串,分别统计出其中的英文字母,数字和其他字符的个数。 string str; cout << \"请输入一行字符串:\" <> str; int num=0, letter=0, other=0; for(int i=0;i=\'0\' && str.at(i)=\'a\' && str.at(i)=\'A\' && str.at(i)<= \'Z\')) letter++; else other++; } cout << \"英文字母个数为:\" << letter << endl; cout << \"数字个数为:\" << num << endl; cout << \"其他字符个数为:\" << other << endl; return 0;}

2、封装(重点)

       MobilePhone类与结构体差别不大,实际上可以认为结构体就是一种完全开放的类。

       封装指的是,将类的一些属性和细节隐藏(private)重新提供外部访问接口,封装的优势可以提升代码的安全性,并且可以让程序员更关注上层的结构,而非内部细节。

#include using namespace std;// 大驼峰命名法(帕斯卡命名法)// 每个单词的首字母要大写class MobilePhone{private:  // 私有权限,private 是最封闭的权限,只能在内类访问 string brand; // 品牌 string model; // 型号 int weight = 200; // 重量public:  // 权限:public最开放的权限 // get 函数 string get_brand() { return brand; } // set 函数 void set_brand(string b) { brand = b; } // get 函数 string get_model() { return model; } // set 函数 void set_model(string m) { model = m; } // get 函数 int get_weight() { return weight; }};int main(){ MobilePhone *mp = new MobilePhone; // 堆内存对象 mp->set_brand(\"小辣椒\"); mp->set_model(\"超辣\"); cout <get_brand() << \" \" <get_model() << \" \" <get_weight() << endl; delete mp; // 手动销毁 mp = NULL; // 建议指向空,防止后面误用 return 0;}

3、构造函数(掌握)

3.1 基本使用

       构造函数是一种特殊的成员函数用于创建对象时初始化创建对象时必须直接或者间接调用当前类任意一个构造函数。

写法上有以下要求:

● 函数名称必须与类名完全相同

● 构造函数不写返回值

● 如果程序员不手写构造函数,编译器会自动添加一个无参的构造函数

手动添加任意一个构造函数,编译器就不会自动添加默认无参构造函数了。

● 构造函数在创建对象时,常用于给对象的属性赋予初始值。

#include using namespace std;// 大驼峰命名法(帕斯卡命名法)// 每个单词的首字母要大写class MobilePhone{private: // 私有权限,private 是最封闭的权限,只能在内类访问 string brand; // 品牌 string model; // 型号 int weight; // 重量public: // 权限:public最开放的权限 // 编译器自动添加的构造函数 MobilePhone() { brand = \"8848\"; model = \"8848钛金手机-鳄鱼皮-巅峰版\"; weight = 1000; } // get 函数 string get_brand() { return brand; } // get 函数 string get_model() { return model; } // get 函数 int get_weight() { return weight; }};int main(){ MobilePhone *mp = new MobilePhone; // 堆内存对象 cout <get_brand() << \" \" <get_model() << \" \" <get_weight() << endl; delete mp; // 手动销毁 mp = NULL; // 建议指向空,防止后面误用 return 0;}

● 构造函数也支持函数重载

#include using namespace std;// 大驼峰命名法(帕斯卡命名法)// 每个单词的首字母要大写class MobilePhone{private: // 私有权限,private 是最封闭的权限,只能在内类访问 string brand; // 品牌 string model; // 型号 int weight; // 重量public: // 权限:public最开放的权限 // 无参构造函数 MobilePhone() { brand = \"8848\"; model = \"8848钛金手机-鳄鱼皮-巅峰版\"; weight = 1000; } // 有参构造函数 MobilePhone(string b, string m, int w) { brand = b; model = m; weight = w; } // get 函数 string get_brand() { return brand; } // get 函数 string get_model() { return model; } // get 函数 int get_weight() { return weight; }};int main(){ MobilePhone *mp = new MobilePhone(\"小米\",\"18 pro\",200); // 堆内存对象 cout <get_brand() << \" \" <get_model() << \" \" <get_weight() << endl; delete mp; // 手动销毁 mp = NULL; // 建议指向空,防止后面误用 return 0;}

● 构造函数也支持函数参数默认值

#include using namespace std;// 大驼峰命名法(帕斯卡命名法)// 每个单词的首字母要大写class MobilePhone{private: // 私有权限,private 是最封闭的权限,只能在内类访问 string brand; // 品牌 string model; // 型号 int weight; // 重量public: // 权限:public最开放的权限 // 无参构造函数 // 下面两种写法的构造函数,都可以给对象做初始化,但是不能同时存在,因为会触发二义性的问题。// MobilePhone()// {// brand = \"8848\";// model = \"8848钛金手机-鳄鱼皮-巅峰版\";// weight = 1000;// } // 有参构造函数(全缺省构造函数) MobilePhone(string b = \"小米\", string m = \"15pro\", int w = 200) { brand = b; model = m; weight = w; } // get 函数 string get_brand() { return brand; } // get 函数 string get_model() { return model; } // get 函数 int get_weight() { return weight; }};int main(){ MobilePhone *mp = new MobilePhone; // 堆内存对象 cout <get_brand() << \" \" <get_model() << \" \" <get_weight() << endl; delete mp; // 手动销毁 mp = NULL; // 建议指向空,防止后面误用 return 0;}

3.2 构造初始化列表

构造初始化列表是一种更简单的给成员变量赋予初始值的写法

#include using namespace std;// 大驼峰命名法(帕斯卡命名法)// 每个单词的首字母要大写class MobilePhone{private: // 私有权限,private 是最封闭的权限,只能在内类访问 string brand; // 品牌 string model; // 型号 int weight; // 重量public: // 权限:public最开放的权限 // 无参构造函数 MobilePhone() :brand(\"8848\"),model(\"8848钛金手机-鳄鱼皮-巅峰版\"),weight(200){} // 有参构造函数 MobilePhone(string b, string m, int w) :brand(b),model(m),weight(w){} // get 函数 string get_brand() { return brand; } // get 函数 string get_model() { return model; } // get 函数 int get_weight() { return weight; }};int main(){ MobilePhone *mp = new MobilePhone(\"华为\",\"18\",200); // 堆内存对象 cout <get_brand() << \" \" <get_model() << \" \" <get_weight() << endl; delete mp; // 手动销毁 mp = NULL; // 建议指向空,防止后面误用 return 0;}

       当构造函数的局部变量与成员变量重名时,除了使用后面学习的this指针的方式外,还可以使用构造初始化列表区分

3.3 隐式调用和显式调用

       构造函数的调用可以分为显式调用隐式调用

       显式调用指的是创建对象的时候手写构造函数的名称

       隐式调用指的是在创建对象的时候不写参数列表,或者构造函数的名称也不写编译器会尝试调用对应参数的构造函数。

#include using namespace std;class Student{private: int age;public: Student(int a):age(a) { cout << \"构造函数被调用啦\" << endl; } Student() { cout << \"无参构造函数被调用啦\" << endl; } int get_age() { return age; }};int main(){ Student s1(12); // 栈内存对象,显式调用 Student s3 = Student(14); // 显式调用 Student s4 = 15; // 隐式调用 Student *s2 = new Student(13); // 显式调用 Student s5; // 默认调用方式,显式调用 return 0;}

建议使用显式调用,可以使用explicit关键字屏蔽隐式调用的语法

3.4 拷贝构造函数

3.4.1 概念

       当程序员不手写拷贝构造函数时,编译器会自动添加一个拷贝构造函数,使对象创建可以通过这个构造函数实现。

#include using namespace std;class Student{private: int age;public: Student(int a):age(a) { cout << \"构造函数被调用啦\" << endl; } Student() { cout << \"无参构造函数被调用啦\" << endl; } // 手动添加默认的拷贝构造函数 Student(Student &mp) { age = mp.age; } int get_age() { return age; }};int main(){ Student s1(12); // 调用有参构造函数 cout << s1.get_age() << endl; // 12 Student s2(s1); // 拷贝构造函数 cout << s2.get_age() << endl; // 12 return 0;}
【思考】默认的拷贝构造函数是否存在隐患?

       存在,当成员变量出现指针类型时,默认的拷贝构造函数会导致两个对象的指针成员变量指向同一处。这种现象被称为”浅拷贝“。

3.4.2 浅拷贝
#include #include using namespace std;class Dog{private: char *name;public: Dog(char *n) { name = n; } void show_name() { cout << name << endl; }};int main(){ char arr[20] = \"旺财\"; Dog d1(arr); Dog d2(d1); // 拷贝构造函数 strcpy(arr,\"大黄\"); // 浅拷贝,更改外部内存,对象内部的数据也被更改了,因为是操作的同一块空间。 d1.show_name(); // 大黄 d2.show_name(); // 大黄 return 0;}

       这种情况必须手动重写构造函数,使每次赋值都创建一个新的副本,从而每个对象单独持有自己的成员变量。这种方式也被称为”深拷贝“。

3.4.3 深拷贝

#include #include using namespace std;class Dog{private: char *name;public: Dog(char *n) { name = new char[20]; strcpy(name,n); } Dog(Dog &d) { name = new char[20]; strcpy(name,d.name); } void show_name() { cout << name << endl; }};int main(){ char arr[20] = \"旺财\"; Dog d1(arr); Dog d2(d1); // 拷贝构造函数 strcpy(arr,\"大黄\"); // 浅拷贝,更改外部内存,对象内部的数据也被更改了,因为是操作的同一块空间。 d1.show_name(); // 旺财 d2.show_name(); // 旺财 return 0;}
【思考】当前深拷贝的代码是否存在隐患?

存在,new开辟的堆内存空间,没有释放,造成内存泄漏的问题。

4、  析构函数(析构函数)

析构函数是与构造函数完全对立的函数。

编译器会自动添加默认没有任何操作的析构函数。

构造函数

析构函数

创建对象时手动调用

当对象销毁时,自动调用

函数名称是类名

函数名称是~类名

构造函数可以重载

析构函数没有参数,不能重载

用于创建对象时做初始化的

用于销毁对象时释放资源

有返回值但是不写,返回值是新创建的对象

没有返回值

#include #include using namespace std;class Dog{private: char *name;public: Dog(char *n) { name = new char[20]; strcpy(name,n); } Dog(Dog &d) { name = new char[20]; strcpy(name,d.name); } void show_name() { cout << name << endl; } ~Dog() { cout << \"析构函数被调用了\" << endl; delete []name; }};int main(){ char arr[20] = \"旺财\"; Dog d1(arr); Dog d2(d1); // 拷贝构造函数 strcpy(arr,\"大黄\"); // 浅拷贝,更改外部内存,对象内部的数据也被更改了,因为是操作的同一块空间。 d1.show_name(); // 旺财 d2.show_name(); // 旺财 return 0;}

5、作用域限定符::

5.1 名字空间(熟悉)

namespace my_space     //声明一个名字空间

using namespace my_space //使用名字空间

#include #include using namespace std;int a = 2;namespace my_space{ int a = 3; int b = 4;}using namespace my_space;int main(){ int a = 1; cout << a << endl; // 就近 打印1 cout << ::a << endl; // ::a 全局作用域 2 cout << my_space::a << endl; // 3 cout << b << endl; return 0;}

5.2 类内声明,类外定义(掌握)

       类外定义,加 “ 类名::”,告诉编译器是哪个类的成员函数,定义写在类外,简洁,代码实现性更高。

#include using namespace std;class Demo{public: // 类内声明 Demo(); void test(string str);};// 类外定义Demo::Demo(){ cout << \"创建了一个对象\" << endl;}void Demo::test(string str){ cout << \"string::\" << str << endl;}int main(){ Demo d; // 调用无参构造函数 d.test(\"hello\"); return 0;}

6、this指针(掌握)

6.1 概念

       this指针是一个特殊的指针,指向当前类对象的首地址

       成员函数(包括构造函数与析构函数)中都有this指针,因此this指针只能在类内部使用。实际上this指向的就是当前运行的成员函数所绑定的对象

#include using namespace std;class Test{public: void test_this() { cout << this << endl; }};int main(){ Test t1; cout << &t1 << endl; // 0x61fe8f t1.test_this(); // 0x61fe8f Test *t2 = new Test; cout << t2 <test_this(); // 0x781108 delete t2; return 0;}

6.2 功能

6.2.1 类内调用成员
#include using namespace std;class Test{private: string name;public: Test(string n) { // 编译器默认添加this指针 指向的对象调用成员 this->name = n; } string get_name() { // 编译器默认添加this指针 指向的对象调用成员 return this->name; }};int main(){ Test t1(\"zhangsan\"); cout << t1.get_name() << endl; return 0;}
6.2.2 区分重名的成员变量和局部变量
#include using namespace std;class Test{private: string name;public: Test(string name):name(name) // 构造初始化列表可以区分 { // 通过this在函数体中区分 this->name = name; } string get_name() { return name; }};int main(){ Test t1(\"zhangsan\"); cout << t1.get_name() << endl; return 0;}
6.2.3 链式调用

支持链式调用的成员函数的特点:

1、  当一个成员函数的返回值是当前类型的引用时,往往表示这个函数支持链式调用。

2、  return后面是*this

#include using namespace std;class Test{private: int val = 0;public: Test& add(int i) { val += i; // val = val + i; return *this; // this 是一个指针,返回当前对象需要取内容 } int get_val() { return val; }};int main(){ Test t1; t1.add(1); t1.add(2); t1.add(100); cout << t1.get_val() << endl; // 103 Test t2; // 链式调用 cout << t2.add(2).add(21).add(200).get_val() << endl; // 223 cout << t2.get_val() << endl; // 223 return 0;}

[练习]:猫吃鱼

 写一个类Fish,有品种和重量两个属性,属性的类型自己选择,要求属性封装。
写一个类Cat,Cat中有一个公有的成员函数:
Fish& eat(Fish &f);
eat函数的功能要求判断Fish的品种:
●如果品种是“秋刀鱼”,则输出“无论多沉,我都爱吃。”。同时修改Fish &f的重量为0,并作为函数的返回值返回。
●如果品种不是“秋刀鱼”,则判断鱼的重量,若重量大于200,吃鱼输出信息并返回一个重量为0的Fish;若重量小于200,输出信息,不要修改鱼的重量,直接返回鱼的对象。

其它知识点(构造函数、构造初始化列表等)可以自行选择是否采用。

代码示例:

#include using namespace std;class Fish{private: string variety; int weight;public: //有参构造函数-构造初始化列表 Fish(string v,int w) :variety(v),weight(w){} string get_variety() { return variety; } int get_weight() { return weight; } int set_weight(int w) { weight=w; }};class Cat{public: Cat& eat(Fish &f) { if(f.get_variety() == \"秋刀鱼\") { cout << \"无论多沉,我都爱吃。\" << endl; f.set_weight(0); cout << \"剩余重量:\" << f.get_weight() <200) { cout << \"不是秋刀鱼我也爱吃!\" << endl; f.set_weight(0); cout << \"剩余重量:\"<< f.get_weight() << endl; } else { cout << \"我不饿,先不吃鱼了\" << endl; cout <<\"剩余重量:\" << f.get_weight() << endl; } } return *this; }};int main(){  while(1) { int a; string str; cout << \"想吃什么鱼?想吃多重的?\" <> str >> a; Fish f(str,a); Cat cat; cat.eat(f); } // Fish f1(\"秋刀鱼\",200); // Fish f2(\"鲅鱼\",800); // Fish f3(\"鳗鱼\",150); //链式调用 // cat.eat(f1).eat(f2).eat(f3); return 0;}

7、static关键字(掌握)

7.1静态局部变量

       使用static修饰局部变量,这样的变量就是静态局部变量。

       静态局部变量在第一次调用时创建,直到程序结束后销毁,同一个类的所有对象共用这一份静态局部变量。

#include using namespace std;class Test{public: void func() { int a = 1; static int b = 1; cout << \"a=\" << ++a << endl; cout << \"b=\" << ++b << endl; }};int main(){ Test t1; t1.func(); //a=2 b=2 t1.func(); //a=2 b=3 Test t2; t2.func(); //a=2 b=4 return 0;}

7.2 静态成员变量

       使用static修饰成员变量,这样的变量就是静态成员变量。

       静态成员变量需要在类内声明,类外定义。

       一个类的所有对象共用一份静态成员变量,虽然静态成员变量可以使用对象调用,但是更建议直接使用类名::调用。静态成员变量可以脱离对象使用,在程序运行时就开辟内存空间,直到程序运行结束后销毁。

#include using namespace std;class Test{public: int a = 1;// static int b = 2; // 错误,静态成员变量需要类内声明。类外初始化 static int b;};int Test::b = 1;int main(){ cout << Test::b << \" \" << &Test::b << endl; Test t1; cout << ++t1.a << \" \" << &t1.a << endl; // 2 cout << ++t1.b << \" \" << &t1.b << endl; // 2 cout << \"------------------\" << endl; Test t2; cout << ++t2.a << \" \" << &t2.a << endl; // 2 cout << ++t2.b << \" \" << &t2.b << endl; // 3 cout << Test::b << \" \" << &Test::b << endl; return 0;}

7.3 静态成员函数

       使用static修饰的成员函数,这样的函数就是静态成员函数。

       可以通过类名直接调用,也可以通过对象调用(推荐使用类名直接调用);

      静态成员函数没有this指针,不能在静态成员函数中调用同类的非静态成员,但是静态成员函数,可以调用静态成员。

#include using namespace std;class Test{public: int a = 10; void func0() { cout << a << endl;// func1(); // 非静态成员函数可以调用静态成员函数 cout << \"非静态成员函数\" << endl; } static void func1() {// func0(); // 错误 静态成员函数,无法直接调用非静态成员函数,因为没有this指针 cout << \"静态成员函数1\" << endl; } static void func2() { func1(); cout << \"静态成员函数2\" << endl; }};int main(){// Test::func1(); Test t1;// t1.func0();// t1.func1(); t1.func2(); return 0;}
【思考】如果要在静态成员函数中调用当前类的非静态成员,要如何实现?

1.通过传参

2.直接创建对象

#include using namespace std;class Test{public: int a = 10; void func0() { cout << a << endl; cout << \"非静态成员函数\" << endl; } static void func1(Test &t1) //传参 {// Test t1; // 直接创建对象 cout << \"静态成员函数1\" << endl; } static void func2() { cout << \"静态成员函数2\" << endl; }};int main(){ Test t1; t1.func1(t1); return 0;}

7.4 单例设计模式(了解)

       设计模式是一套被反复使用,多人知晓的,经过分类的,代码设计经验的总结。通常用到一些面向对象的语言中,如:C++、JAVA、C#等等

#include using namespace std;/** * @brief The Singleton class * 单例模式:只能创建一个对象(可以多次) */class Singleton{private: Singleton(){} Singleton(const Singleton&); static Singleton* instance; // 静态成员变量public: static Singleton* get_instance() // 静态成员函数 { if(instance == NULL) instance = new Singleton; return instance; } static void delete_instance() { if(instance != NULL) { delete instance; instance = NULL; } }};Singleton* Singleton::instance = NULL;int main(){ Singleton* s1 = Singleton::get_instance(); Singleton* s2 = Singleton::get_instance(); cout << s1 << endl; cout << s2 << endl; return 0;}

8、  const关键字(掌握)

8.1 const修饰成员函数

const修饰的成员函数,表示常成员函数

特性如下:

● 可以调用成员变量,但是不能修改成员变量的值。

● 不能调用非const的成员函数,哪怕这个函数并没有修改成员变量。

#include using namespace std;class Demo{private: int a;public: Demo(int a):a(a) {} void func0() { cout << \"哈哈哈哈哈\" << endl; } // 常成员函数 int get_a() const { return a; } void test() const { // 错误 const修饰的成员函数,不能修改成员变量// a++; cout << a << endl;// func0(); 错误 const修饰的成员函数,不能调用非const修饰的成员函数 get_a(); }};int main(){ Demo demo(1); cout << demo.get_a() << endl; // 1 demo.func0(); demo.test(); return 0;}

8.2 const修饰对象

       const修饰的对象被称为常量对象,这种对象的成员变量值无法被修改,也无法调用非const的成员函数。

#include using namespace std;class Demo{private: int a;public: int b = 10; Demo(int a):a(a) { } void func0() { cout << \"哈哈哈哈哈\" << endl; } // 常成员函数 int get_a() const { return a; } void test() const { // 错误 const修饰的成员函数,不能修改成员变量// a++; cout << a << endl;// func0(); 错误 const修饰的成员函数,不能调用非const修饰的成员函数 get_a(); }};int main(){ // 常对象 const Demo demo(1);// Demo const demo(1); // 两种初始化的写法,等效于上一行 cout << demo.get_a() << endl; // 1// demo.func0(); // 错误,const修饰的对象,无法调用非const修饰的成员函数 demo.test();// demo.b = 10; // 错误 const修饰的对象,无法修改成员变量 cout << demo.b << endl; // 可以调用但是无法修改 return 0;}

8.3 const修饰成员变量

const修饰的成员变量为常成员变量,表示该成员变量的值无法被修改。

常成员变量有两种初始化的方式:

● 直接赋值

● 构造初始化列表

上述两种初始化同时使用时,以构造构造初始化列表为准

#include using namespace std;class Demo{private: const int a = 1; const int b = 2; const int c = 3;public: Demo(int a,int b,int c):a(a),b(b),c(c) { } void show() { cout << a << \" \" << b << \" \" << c << endl; } void test() {// a++;// b++;// c++;// a = 10;// b = 20;// c = 30; }};int main(){ Demo d1(10,20,30); d1.show(); return 0;}

8.4 const修饰局部变量

const修饰局部变量,表示该局部变量不可被修改。

这种方式常用于引用参数

#include using namespace std;class Demo{private: const int a = 1; const int b = 2; const int c = 3;public: Demo(int a,int b,int c):a(a),b(b),c(c) { } void show() { cout << a << \" \" << b << \" \" << c << endl; } void test(const int &f) { // const修饰局部变量 const int e = 1;// e = 40; // 错误 const修饰局部变量不能被修改 cout << e << endl; }};int main(){ int a = 20; Demo d1(10,20,30); d1.show(); d1.test(a); return 0;}

四、运算符重载

1、  友元(熟悉)

1.1 概念

类主要实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,仅能够通过类的成员函数才能读写。如果数据成员定义成公共的,则又破坏了封装性,但是在某些情况下,需要频发的读写类数据成员,特别是对某些成员函数多次调用时,由于参数传递,类型检查、和安全性检查等等都需要时间开销,而影响程序的运行效率。

友元有三种实现方式:

● 友元函数

● 友元类

● 友元成员函数

友元函数是一种定义在类外部的普通函数,但他还需要再类体内进行声明,为了和该类的成员函数加以区分,再声明前面加一个关键字friend。友元函数不是成员函数,但是它能够访问类中的所有成员,包括私有成员。

友元在于提高程序的运行效率,但是它破坏了类的封装性和隐藏性,使得非成员函数也能够访问类的私有成员,导致程序维护性变差,因此使用友元要慎重

1.2 友元函数

       友元函数不属于任何一个类,是一个类外的函数,但是再类内需要声明。虽然友元函数不是成员函数,但是却可以访问类中的所有成员(包括私有成员)。

#include using namespace std;class Test{private: int a;public: Test(int i):a(i){} void show() { cout << a << \" \" << &a << endl; } // 友元函数,类内声明 friend void and_test(Test &t);};// 友元函数void and_test(Test &t){ cout << t.a << \" \" << &t.a << endl;}int main(){ Test t1(1); and_test(t1); // 1 0x61fe8c t1.show(); // 1 0x61fe8c return 0;}

友元函数的使用需要注意以下几点:

● 友元函数没有this指针

● 友元函数的声明,可以放在类中的任何位置,不受权限修饰符的影响。

● 一个友元函数可以访问多个类,只需要再各个类中分别“声明”,然后把对象传递进来。

1.3 友元类

       当一个类B成为了另一个类Test的朋友时,类Test的所有成员都可以被类B访问,此时类B就是类Test的友元类。

#include using namespace std;class Test{private: int a;public: Test(int i):a(i){} void show() { cout << a << \" \" << &a << endl; } // 友元类,类内声明 friend class B;};class B{public: void and_test(Test &t) { cout << t.a << \" \" << &t.a << endl; } void and_test1(Test &t) { cout << t.a << \" \" << &t.a << endl; } void and_test2(Test &t) { cout << t.a << \" \" << &t.a << endl; }};int main(){ Test t1(2); B b; b.and_test(t1); // 2 0x61fe8c b.and_test1(t1); b.and_test2(t1); t1.show(); // 2 0x61fe8c return 0;}

友元类的使用需要注意以下几点:

● 友元关系不能被继承

● 友元关系不具有交换性(比如:类B声明为类Test的友元,类B可以访问类Test的成员,但是类Test不能访问类B的私有成员,需要需要访问,需要将类Test声明成类B的友元)

互为友元代码,需要类内声明,类外实现。

#include using namespace std;class Cat;class Test{private: int a;public: Test(int i):a(i){} void test(Cat &c); friend class Cat;};class Cat{private: int b;public: Cat(int i):b(i){} void test1(Test &t); friend class Test;};void Test::test(Cat &c){ cout <<c.b<<endl;}void Cat::test1(Test &t){ cout <<t.a++<<endl;}int main(){ Test t(44); Cat c(12); c.test1(t); return 0;}

1.4 友元成员函数

       使类B中的成员函数成为类Test的友元成员函数,这样类B的该成员函数就可以访问类Test的所有成员了。

#include using namespace std;// 第四步:声明被访问的类class Test;class B{public: // 第二步:声明友元成员函数(需要:类内声明。类外定义) void and_test(Test &t);};class Test{private: int a;public: Test(int i):a(i){} void show() { cout << a << \" \" << &a << endl; } // 友元成员函数,类内声明(第一步) friend void B::and_test(Test &t);};// 第三步:类外定义友元成员函数void B::and_test(Test &t){ cout << t.a << \" \" << &t.a << endl;}int main(){ Test t1(2); B b; b.and_test(t1); // 2 0x61fe8c t1.show(); // 2 0x61fe8c return 0;}

2、  运算符重载(掌握)

2.1 概念

C++中可以把部分运算符看做成函数,此时运算符也可以重载。

运算符预定义的操作只能针对基本数据类型,但是对于自定义类型,也需要类似的运算操作时,此时就可以重新定义这些运算符的功能,使其支持特定类型,完成特定的操作。

运算符重载有两种实现的方式:

● 友元函数运算符重载

● 成员函数运算符重载

2.2 友元函数运算符重载

#include using namespace std;class MyInt{private: int a;public: MyInt(int a):a(a){} int get_int() { return a; } // + 运算符重载 friend MyInt operator +(MyInt &i,MyInt &i2);};MyInt operator +(MyInt &i,MyInt &i2){// MyInt int4(0);// int4.a = i.a + i2.a;// return int4; // int → MyInt 触发隐式调用构造函数// MyInt n1 = 4; return i.a + i2.a;}int main(){ MyInt int1(2); MyInt int2(int1); // 拷贝构造函数 MyInt int3 = int1 + int2; cout << int3.get_int() << endl; // 4 return 0;}

++ 运算符重载:

#include using namespace std;class MyInt{private: int a;public: MyInt(int a):a(a){} int get_int() { return a; } // + 运算符重载 friend MyInt operator +(MyInt &i,MyInt &i2); friend MyInt operator ++(MyInt &i); // 前置自增 friend MyInt operator ++(MyInt &i,int); // 后置自增};MyInt operator +(MyInt &i,MyInt &i2){ return i.a + i2.a;}MyInt operator ++(MyInt &i){ return ++i.a;}MyInt operator ++(MyInt &i,int){ return i.a++;}int main(){ MyInt int1(2); MyInt int2(int1); // 拷贝构造函数 MyInt int3 = int1 + int2; cout << int3.get_int() << endl; // 4 cout << (++int1).get_int() << endl; // 3 cout << (int1++).get_int() << endl; // 3 cout << int1.get_int() << endl; // 4 return 0;}

2.3 成员函数运算符重载

       成员函数运算符重载相比于友元函数运算符重载,最主要的区别在于,由于函数的第一个传入参数,在成员函数运算符重载中使用this指针代替,因此同样的运算符重载,成员函数比友元函数重载参数少一个。

#include using namespace std;class MyInt{private: int a;public: MyInt(int a):a(a){} int get_int() { return a; } // + 运算符重载 MyInt operator +(MyInt &i2); MyInt operator ++(); // 前置自增 MyInt operator ++(int); // 后置自增};MyInt MyInt::operator +(MyInt &i2){ return this->a + i2.a;}MyInt MyInt::operator ++(){ return ++this->a;}MyInt MyInt::operator ++(int){ return this->a++;}int main(){ MyInt int1(2); MyInt int2(int1); // 拷贝构造函数 MyInt int3 = int1 + int2; cout << int3.get_int() << endl; // 4 cout << (++int1).get_int() << endl; // 3 cout << (int1++).get_int() << endl; // 3 cout << int1.get_int() << endl; // 4 return 0;}

2.4 特殊运算符重载

2.4.1 赋值运算符重载

       除了之前学习的无参构造函数,拷贝构造函数与析构函数以外,如果程序员不收手写,编译器会给一个类添加赋值运算符重载函数。

#include using namespace std;class MyInt{private: int a;public: MyInt(int a):a(a){} int get_int() { return a; } // 编译器会自动添加赋值运算符重载函数 MyInt &operator =(MyInt &i) { cout << \"运算符重载函数被调用了\" << endl; this->a = i.a; return *this; } // + 运算符重载 MyInt operator +(MyInt &i2); MyInt operator ++(); // 前置自增 MyInt operator ++(int); // 后置自增};MyInt MyInt::operator +(MyInt &i2){ return this->a + i2.a;}MyInt MyInt::operator ++(){ return ++this->a;}MyInt MyInt::operator ++(int){ return this->a++;}int main(){ MyInt int1(2); MyInt int2(int1); // 拷贝构造函数 MyInt int3 = int1 + int2; int3 = int1; cout << int3.get_int() << endl; return 0;}

       当类中出现指针类型的成员变量时,默认的赋值运算符重载函数类似于默认的浅拷贝构造函数,因此也需要手动编写解决”浅拷贝“的问题。

【面试题】一个类什么也不写,编译器添加了那些函数?

无参构造函数、拷贝构造函数、析构函数、赋值运算符重载函数

【面试题】一个类权限也不写,默认是什么权限?

默认为私有权限。

【面试题】一个空类的大小是多少大?

1个字节。

2.4.2 类型转换运算符重载

必须使用成员函数运算符重载,且格式比较特殊。

#include using namespace std;class MyInt{private: int a;public: MyInt(int a):a(a){} int get_int() { return a; } // 编译器会自动添加赋值运算符重载函数 MyInt &operator =(MyInt &i) { cout << \"运算符重载函数被调用了\" << endl; this->a = i.a; return *this; } // + 运算符重载 MyInt operator +(MyInt &i2); MyInt operator ++(); // 前置自增 MyInt operator ++(int); // 后置自增 // 类型转换运算符重载 operator int() { return a; } operator string() { return \"hello\"; }};MyInt MyInt::operator +(MyInt &i2){ return this->a + i2.a;}MyInt MyInt::operator ++(){ return ++this->a;}MyInt MyInt::operator ++(int){ return this->a++;}int main(){ MyInt int1(2); MyInt int2(int1); // 拷贝构造函数 MyInt int3 = int1; // 拷贝 int2 = int3; // 赋值运算符重载 int a1 = int2; cout << a1 << endl; // 2 string s2 = int3; cout << s2 << endl; // hello return 0;}

2.5 注意事项

● 重载的运算符限制在C++语言中已有的运算符范围,即不能创建新的运算符。

● 运算符重载的本质上也是函数重载,但是不支持函数参数默认值的设定。

● 重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符的操作数和语法结构。

● 运算符重载必须基于或者包含自定义类型,不能改变基本数据类的运算规则。

● 重载的功能应该与原有的功能相似,避免没有目的的滥用运算符重载

● 一般情况下,双目运算符建议使用友元函数重载,单目运算符建议使用成员函数运算符重载。

3、  std::string 字符串类(熟悉)

字符串对象是一种特殊类型的容器,专门设计用于操作字符串。

#include #include using namespace std;int main(){ string s; // 创建一个空的字符串 // 判断是否为空 cout << s.empty() << endl; // 1 // 隐式调用构造函数 string s1 = \"hello\"; cout << s1 << endl; // hello // 构造函数显式调用 string s2(\"world\"); cout << s2 << endl; // world // ==、!=、>、< 判断编码 cout << (s1 == s2) << endl; // 0 cout << (s1 != s2) << endl; // 1 cout << (s1 > s2) << endl; // 0 cout << (s1 < s2) << endl; // 1 // 拷贝构造函数 string s3(s2); cout << s3 << endl; // world // 参数1:char* 源字符串 // 参数2:保留的字符数 string s4(\"ABCDEFG\",3); cout << s4 << endl; // ABC // 参数1:std::string 源字符串 // 参数2:不保留的字符数,从头开始 string s5(s2,3); cout << s5 << endl; // ld // 参数1:字符数量 // 参数2:字符内容 string s6(5,\'a\'); cout << s6 << endl; // aaaaa // 交换 cout << \"s5 = \" << s5 << \" \" << \"s6 = \" << s6 << endl; swap(s5,s6); // 交换后:aaaaa ld cout << \"s5 = \" << s5 << \" \" << \"s6 = \" << s6 << endl; // 字符串拼接 string s7 = s5 + s6; cout << s7 << endl; // aaaaald // 向后追加字符串 s7.append(\"jiajia\"); cout << s7 << endl; // aaaaaldjiajia // 向后追加单字符 s7.push_back(\'s\'); cout << s7 << endl; // aaaaaldjiajias // 插入 // 参数1:插入的位置 // 参数2:插入的内容 s7.insert(1,\"234\"); cout << s7 << endl; // a234aaaaldjiajias // 删除字符串 // 参数1:起始位置 // 参数2:删除的字符数量 s7.erase(2,5); cout << s7 << endl; // a2aldjiajias // 替换 // 参数1:起始位置 // 参数2:被替换的字符数 // 参数3:替换的新内容 s7.replace(0,3,\"***\"); cout << s7 << endl; // ***ldjiajias // 清空 s7.clear(); cout << s7.length() << endl; // 0 // 直接赋值初始化 string s8 = \"hahaha\"; cout << s8 << endl; // 重新赋值 s8 = \"ABCDEFGH\"; cout << s8 << endl; // ABCDEFGH // 参数1:拷贝的目标 // 参数2:拷贝的字符数量 // 参数3:拷贝的起始位置 // C++的string 到 C语言的string也就是字符数组 char arr[20] = {0}; s8.copy(arr,6,1); cout << arr << endl; // BCDEFG // C++string 到C string 用到了C语言中strcpy // c_str C++的字符串转换成C语言的字符数组 // c_str 返回一个const char * char c[20] = {0}; strcpy(c,s8.c_str()); cout << c << endl; // ABCDEFGH return 0;}