【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;}
【面试题】
指针和引用的区别?
指针:是一个变量,存储另一个变量的内存地址。
需要显式解引用(*
)来访问目标值。
引用:是变量的别名,本质是绑定到目标变量的“另一个名字”。
无需解引用,直接操作引用即操作原变量。
核心区别
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
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;}