一、C++特点
在支持C语言的基础上,全面支持面向对象开发。
编程领域广泛,功能强大(最难的编程语言之一)。
标准保持更新,本次课程以ISO C++98标准为主,以ISO C++11标准为辅。
为数不多的支持底层操作的面向对象语言。
在面向对象的编程语言中执行效率极高
1.重要技术点
类 class
对象 object、instance......
面向对象的三大特性:封装 → 继承 → 多态
面向过程和面向对象
面向过程是以怎么解决问题为核心
面向对象是以谁来解决问题为核心
二.从c到c++
1.引用
1.1概念
引用与指针类似,但是使用更加简便,功能更加简单,可以认为引用是一个变量的“别名”,对引用进行操作与直接操作变量完全相同。
1.2性质
1. 可以改变引用的变量值,但是不能再次成为其它变量的引用。
2. 声明引用时,必须对其进行初始化。
3. 声明引用时初始化的值不能是NULL
4. 声明引用时,初始化的值如果是纯数值,需要给引用增加const关键字修饰,表示该引用是常引用,这样的引用不能改变其数值。
5. 可以将变量引用的地址赋值给一个指针,此时指针指向的还是原来的变量。
6. 可以对指针建立引用。
7. 可以使用const对引用进行修饰,此时虽然不能直接改变引用的值,但是可以间接改变原变量值。
1.3引用参数
使用引用参数还可以使参数传递的效率提高,因为不产生副本。
引用参数应该在能被定义为const的情况下,尽量定义const,以达到引用的安全性。
1.4赋值
C++中除了可以使用=赋值外,还可以使用下面的方式赋值。
C++11中增加了数据窄化的写法与提示。
1.5键盘输入
在C++中可以使用cin来获得键盘输入,也支持连续输入,也在头文件iostream里。连续输入的内容可以使用空格或回车来分割。
cin输入的字符串类型是string,这是C++的字符串类型。
2.string 字符串
2.1基础使用
string并不是C++的基本数据类型,string是C++内置的字符串类,使用时需要引入头文件<string>,而不是<string.h>,string在绝大多数情况下可以代替char*,不必担心内存是否足够和字符串长度。
string内部集成了大量的字符串处理函数。
string使用ASCII编码,只支持英文字符,严禁使用中文字符!!!
2.2字符串实际长度
s.size() s.length()
2.3取出元素
string不光支持中括号 [ ] 的方式取出元素,还支持使用at函数的方式取出元素。
这两种方式的区别是:
前者取出的效率更高
后者更加安全
3.函数
3.1内联函数 inline
内联函数的目的是取代宏定义的函数,使用关键字inline放在函数定义(非声明)的前面,可以将函数制定为内联函数。
内联函数在编译时,会直接展开函数体到主函数中编译,因此可以提升程序的运行效率。一般将代码长度较小(5行以内,不能包含复杂的控制语句)且频繁使用的函数写为内联函数。
所有的成员函数默认为内联函数,无需手动使用inline修饰。
3.2函数重载 overload
C++中允许使用同一个函数名臣定义多个函数,这就是函数重载。函数重载的前提是各个重载的函数之间参数(类型或个数)不同,与返回值类型无关。
除了上述的普通函数支持重载外,成员函数和构造函数等也支持函数重载,但是析构函数不支持函数重载。
3.3函数的参数默认值(缺省值)
C++中允许给函数的参数设定默认值,在调用函数时,如果传入参数,则传入的参数会覆盖默认值;如果不传入参数,则使用默认值作为参数值。
参数的默认值只允许在声明和定义中出现一次。
向右(后)原则:如果函数参数有多个,此时给某个参数设定了默认值后,其右边(后边)所有的参数都必须设定默认值。
3.4哑元函数
如果一个函数的参数只有类型,没有名字,这个参数在函数体中无法使用,这样的参数被称为哑元,这样的函数被称为哑元函数
哑元函数主要用途:
①区分重载函数 后面在运算符重载中使用
②保持函数的向前兼容性
Tips
快捷键:
Alt + 0 隐藏/显示边栏
Alt + 左或右 光标回到上次编辑位置/前进到更新的编辑位置
先Ctrl + A,再Ctrl + I 对齐代码
Ctrl + Z 撤销
Ctrl + Y 重做
Ctrl + R 运行
三、面向对象基础
1.类与对象的概念
1.1类:类是对同一类对象的抽象总结,是一个概念。
1.1.1属性
用来描述对象的数据元素,通常是一个名词变量,例如:身高、体重、价格等,也称为“成员变量”或“数据成员”。
1.1.2行为
用来描述对象执行的具体操作,通常对属性进行操作,以动词函数的方式存在,例如:吃饭、睡觉、运行等,也称为“成员函数”或“成员方法”。成员变量和成员函数统称为“成员”。
对象:按照类的规定创建的实体。
2.类的定义
【例子】以手机为例,来说明类的定义。规定手机的属性:品牌、型号和重量
手机的成员函数:运行游戏、播放音乐、通信
class MobilePhone // 帕斯卡命名法:所有单词的首字母大写
{
public: // 公有权限:最开放的一种权限
string brand; // 品牌
string model; // 型号
int weight; // 重量
void run_game() // 运行游戏
{
cout << "timi" << endl;
}
void play_music()
{
cout << "只因你太美" << endl;
}
void communicate()
{
cout << "你好" << endl;
}
};
帕斯卡命名法 所有单词首字母大写
3.实例化对象
3.1栈内存对象
在生命手指其结束(所在花括号执行完)后,自动被销毁
栈内存对象使用 . 调用成员
3.2堆内存对象
需要使用 new 关键字创建, 使用delete 关键字销毁 ,如果不销毁,则会持续存在,容易导致内存泄露的问题,内存泄露最终可能会导致程序卡顿,甚至卡死。
堆内存对象通常使用指针来保存堆内存对象的地址
堆内存对象使用-> 调用成员,在 Qt creator 下,直接打 . 会转换成->
3.3封装
软件测试有黑盒和白盒的概念,之前写的MobilePhone类是一个完全开放的类,所有类的细节都公开,类似于白盒。
封装作为面向对象的三大特性之一,要求将类的一些属性和细节隐藏,并重新公开给外部调用的接口,类似于黑盒。
3.4读写功能
读 getter
写 setter
4构造函数
4.1概念
1.构造函数类内一种特殊的函数,用来创建一个对象。如果一个类中,程序员不手动编写构造函数,编译器会为这个类自动添加一个无参构造函数,且此函数的函数体为空;如果程序员手写了任意一个构造函数,编译器就不再自动添加构造函数了。
2.构造函数要求函数名必须与类名完全一致,且构造函数无需写返回值。
4.2传参
可以给构造函数增加参数,使用参数给属性赋予初始值,是u对象的创建更灵活
4.2.1重载 overload
构造函数也支持函数重载,遵守之前的函数重载规则
4.2.2参数默认值
构造函数也支持之前的参数默认值设定
4.2.3构造初始化列表
构造初始化列表是一种渐变的写法,也可以用于给属性赋予初始值
5.拷贝构造函数
5.1概念
如果程序员在一个类中不手动编写拷贝构造函数,编译器会为这个类自动添加一个拷贝构造函数,拷贝构造函数与普通构造函数也是函数重载的关系。
对象和对象之间是独立存在的实体,数据也是相互独立的,拷贝构造函数可以把一个对象的属性值拷贝到新创建的对象中。
【思考】拷贝构造函数可能会出现什么问题?
如果成员变量出现指针,在拷贝的过程中,会导致两个对象的成员变量指针保存同一份地址,指向同一个内存区域,不符合面向对象的特性。
5.2浅拷贝与深拷贝
当类中的成员变量出现指针,默认的浅拷贝
浅拷贝
此时需要手写构造函数,为每个对象的name属性单独开辟一个内存区域,且当前对象独享这个区域。
深拷贝 只拷贝内容
6.析构函数
析构函数是与构造函数对的函数
构造函数 | 析构函数 |
手动调用 | 在对象被销毁时自动调用 |
通常用于在对象创建时初始化 | 通常用于在对象销毁时回收资源 |
可以被重载 | 不可以重载,因为没有参数 |
函数名是类名 | 函数名是~类名 |
7.作用域限定符 ::
7.1名字空间
std是C++标准库的一个名字空间,很多使用的内容都是来自于标准名字空间,例如字符串std::string、std::cout...
当项目中包含using namespace std;时,代码中使用std名字空间中的内容就可以省略前面的std::
7.2类内声明,类外定义
对于类中的成员也可以声明定义分离,如果声明定义分离,通常在类内声明,在类外定义,类外的定义需要结合作用域限定符使用。
与static关键字配合使用
7.3explicit关键字
如果赋值时,刚好赋值运算符右边的数值是左边类的构造函数可以接收的类型,编译器则会自动调用这个构造函数并把赋值运算符右边的数值传入构造函数中,相当于隐式调用了构造函数。
有时候在一些参数传递的过程中,隐式构造可能会在程序员无意的情况下触发了构造函数,因此可以在构造函数前添加explicit关键字修饰,屏蔽隐式调用的用法。
8.this指针
8.1概念
this指针是一个特殊的指针,保存的是当前类的对象首地址。
实际上可以通过下面的方法判断this的指向:this所在的函数是哪个对象的,this指向的就是这个对象。
8.1.1原理
在类内调用此类的成员,虽然不用手写this指针,但是编译器都会使用this指针来调用成员,因为成员只能由对象来调用,而this指针指向的就是当前类的对象。
应用
8.1.2区分重名变量
当成员变量与局部变量重名时,可以使用this指针调用成员变量。
8.1.3链式调用
如果一个函数的返回值是当前类的引用,那么通常此函数需要返回一个*this,并且此函数支持链式调用。
0+1+2+3+4+5
多态传参 Qt
9.static 关键字
9.1静态局部变量
使用static关键字修饰局部变量就是静态局部变量。
虽然上面的结果中,变量a和变量b的地址始终都没有变化,但是变量a在这个地址上创建了两次,而变量b只在此函数第一次调用时创建了一次。
静态局部变量所在的函数第一次被调用时,静态局部变量创建,在程序结束时才销毁。
9.2静态成员变量
成员变量使用static修饰就是静态成员变量,静态成员变量具有的特点:
此类的所有对象共用此变量
非const的静态成员变量通常需要类内声明,类外初始化
静态成员变量可以直接使用类名::来调用
静态成员变量在程序运行时创建,在程序结束时销毁
9.3静态成员函数
成员函数使用static修饰就是静态成员函数,静态成员函数的特点有:
静态成员函数不能访问此类中非静态成员,因为没有this指针
静态成员函数只能调用本类中静态的成员
非静态成员函数可以调用静态成员
除了可以使用当前类对象调用静态成员函数外,也可以直接使用类名::调用,推荐后者
如果静态成员函数声明与定义分离,只需要在声明处使用static修饰
10.const关键字
在C++中,虽然认为const表示常量的意思,但是严格地讲,const并不是常量。因为C++中const只能在程序的运行期间只读,即在编译期可以改变数值。
10.1常成员函数
const修饰的成员函数,表示常成员函数,这种函数的特点是:
可以调用本类中非const的成员变量,但是不能修改其数值
不能调用非const的成员函数
建议成员函数只要不修改成员变量值就写为常成员函数,例如getter
#include <iostream>
using namespace std;
class Test
{
private:
int a = 1;
public:
void set_a(int a)
{
this->a = a;
}
int get_a() const
{
return a;
}
void func() const // 常成员函数
{
// a++; 错误
cout << a << endl;
// set_a(6); 错误
cout << get_a() << endl;
}
};
int main()
{
Test t;
t.func();
return 0;
}
10.2 常量对象
const修饰对象,表示该对象为常量对象,其特点有:
常量对象的任何属性值不能被修改
常量对象不能调用任何非const的成员函数
const修饰对象时,const关键字可以写在类名前面,也可以类名后面。
10.3 常成员变量
使用const修饰成员变量,表示该成员变量为常成员变量,其特点有:
程序运行时,常成员变量的值不可变
不能在函数体中赋值,只能通过直接赋值或构造初始化列表赋值
10.4 修饰局部变量
类似于之前给引用参数增加const修饰,函数的局部变量都可以使用const修饰,表示常量。
四、运算符重载
1. 友元
1.1 概念
类实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,仅能通过类的成员函数才能读写。如果数据成员定义为公共的,则又破坏了封装性。但是某些情况下,需要频繁读写类的数据成员,特别是在对某些成员函数多次调用时,由于参数传递、类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。
友元是一种定义在类外部的普通函数,但他需要在类内进行说明,为了和该类的成员函数加以区别,在说明时前面加以关键字friend
学习友元最终的目的是把友元运用在运算符重载上,其它情况下不要使用友元,因为会破坏面向对象的特性。
友元主要分为以下几种使用情况:
友元函数
友元类
友元成员函数
1.2 友元函数
友元函数是一种类外的函数,但是需要在类内结合friend关键字进行说明(非声明),需要注意以下几点:
因为友元函数不是类内的函数,因此没有this指针,因此需要给友元函数增加一个传递对象的参数位,用此对象来调用类中的成员。
友元函数不属于类,因此不受类中权限修饰符的影响,即友元函数的说明可以放在类中的任意位置。
一个友元函数可以访问多个类的成员,只需要在多个类中分别说明。
1.3 友元类
当一个类B成为了另一个类A的“朋友”时,类A的所有成员就可以被类B访问,此时类B是类A的友元类。
需要注意的是:
友元关系是单向的,不具有交换性。
友元关系不具有传递性。
友元关系不能被继承。
1.4 友元成员函数
可以使类B中的某一个成员函数成为类A的友元成员函数,这样类B中只有这个成员函数可以访问类A的所有成员。
2. 运算符重载
2.1 概念
函数可以重载,运算符也是一种特殊的函数,因此运算符也可以重载。
函数的组成部分:
名称
输入参数
函数体
返回值
上述的每个组成部分运算符都拥有。
C++中的运算符默认的操作类型只支持基本数据类型,例如+支持整型浮点型等类型的运算,但是对于很多用户自定义的类型(Dog类、Cat类、Test类等)的对象也需要支持运算符,例如 狗+狗。此时可以在代码中重载运算符,赋予这些运算符处理新的类型的功能。
可以被重载的运算符:
算术运算符:+、-、*、/、%、++、--
位操作运算符:&、|、~、^(位异或)、<<(左移)、>>(右移)
逻辑运算符:!、&&、||
比较运算符:<、>、>=、<=、==、!=
赋值运算符:=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=
其他运算符:[]、()、->、,、new、delete、new[]、delete[]
不被重载的运算符:
成员运算符 .、指针运算符 *、三目运算符 ? :、sizeof、作用域 ::
2.2 友元函数运算符重载
可以使用友元函数进行运算符重载。
2.3 成员函数运算符重载
也可以使用成员函数进行运算符重载,成员函数的运算符函数对应的输入参数比同样使用友元函数实现的友元函数的参数少一个。
2.4 其它运算符重载
2.4.1 赋值运算符重载
如果写一个空类,按照目前所学内容,编译器会自动添加:
无参构造函数
析构函数
拷贝构造函数
除了上述三者外,编译器还会增加若干内容,其中包括赋值运算符重载函数。
赋值运算符重载函数只支持成员函数运算符重载,不支持友元函数运算符重载的方式。
通常无需手动编写赋值运算符重载,以下情况需要手动编写,编译器不在自动添加赋值运算符重载函数:
当前类的成员变量出现指针
屏蔽赋值运算符的使用(权限为private)
2.4.2 类型转换运算符重载
可以使自定义类型的对象自动转换为任意类型,此函数也只能使用成员函数运算符重载。
2.5 注意事项
运算符重载限制在C++已有的运算符范围内,不允许创建新的运算符。
运算符重载也是函数重载,运算符也是函数。
重载之后的运算符不能改变优先级和结合性。
重载之后的运算符不能改变操作数和语法结构。
运算符重载不能改变该运算符用于基本数据类型的含义,但是可以把基本数据类型与自定义类型一起运算,或者都使用自定义类型。
运算符重载是针对新类型数据的实际需要对原有运算符的功能进行扩充,因此重载之后的功能应该与原有的功能类似,避免没有目的地使用运算符重载。
通常建议单目运算符使用成员函数运算符重载,双目运算符使用友元函数运算符重载。
3. 字符串类 std::string
再学习此类,主要涉及此类的一些构造函数和成员函数。
std::string是一种特殊容器的类型,用于操作字符序列。
#include <iostream>
#include <string.h>
using namespace std;
int main()
{
string s; // 创建一个内容为空的字符串对象
// 是否为空
cout << s.empty() << endl; // 1
// 隐式调用构造函数
string s1 = "Abc";
// 相当于
string s2("Abc");
cout << (s1 == s2) << endl; // 1
// 拷贝构造函数
string s3 = s2;
string s4(s3);
cout << s3 << " " << s4 << endl; // Abc Abc
// 参数1:源字符串 char*
// 参数2:从前往后保留的字符数
string s5("ABCDEFG",3);
cout << s5 << endl; // ABC
// 参数1:源字符串 string
// 参数2:从前往后不保留的字符数
s = "ABCDEFG";
string s6(s,3);
cout << s6 << endl; // DEFG
// 参数1:字符数量
// 参数2:字符 char
string s7(5,'A');
cout << s7 << endl; // AAAAA
// 交换
swap(s6,s7);
cout << s6 << " " << s7 << endl; // AAAAA DEFG
s1 = "ABCD";
// 向后追加字符串,支持链式调用
s1.append("EF").append("GHI").append("ZZZ"); // ABCDEFGHIZZZ
cout << s1 << endl;
// 字符串连接
s1 = s6+s7;
cout << s1 << endl; // AAAAADEFG
// 向后追加一个字符
s1.push_back('*');
cout << s1 << endl; // AAAAADEFG*
// 参数1:插入的位置
// 参数2:插入的内容
s1.insert(1,"###");
cout << s1 << endl; // A###AAAADEFG*
// 参数1:替换的起始位置
// 参数2:替换的字符数量
// 参数3:替换的新内容
s1.replace(0,7,"!!!!");
cout << s1 << endl; // !!!!ADEFG*
// 参数1:删除的起始位置
// 参数2:删除的字符数
s1.erase(4,3);
cout << s1 << endl; // !!!!FG*
// 清空
s1.clear();
cout << s1.length() << endl; // 0
// C字符串→C++字符串
char c[20] = "hello";
s = c;
cout << s << endl; // hello
// C++字符串→C字符串
s = "Good afternoon!";
// c = s; 错误
s.copy(c,s.size()); // 全拷贝
cout << c << endl; // Good afternoon!
// 参数1:源字符串
// 参数2:拷贝的数量
// 参数3:拷贝的起始位置
s.copy(c,9,5);
cout << c << endl; // afternoonrnoon!
// 还可以使用下面的函数完成
char d[20];
strcpy(d,s.c_str()); // c_str()返回一个临时的const char*
cout << d << endl; // Good afternoon!
return 0;
}