《C++面向对象程序设计》✍千处细节、万字总结
文章目录
《C++面向对象程序设计》✍千处细节、万字总结
一、面向对象程序设计
二、C++基础
2.1 C++的产生和特点
2.2 一个简单的C++示例程序
2.3 C++在非面向对象方面对C语言的扩充
输入和输出
cin
const修饰符
void型指针
内联函数
带有默认参数值的函数
函数重载
作用域标识符"::"
强制类型转换
new和delete运算符
引用
三、类和对象(一)
3.1 类的构成
3.2 成员函数的定义
3.3 对象的定义和使用
3.4 构造函数与析构函数
构造函数
成员初始化列表
带默认参数的构造函数
析构函数
默认的构造函数和析构函数
构造函数的重载
拷贝构造函数
浅拷贝和深拷贝
四、类和对象(二)
4.1 自引用指针this
4.2 对象数组与对象指针
4.3 string类
4.4 向函数传递对象
4.5 静态成员
4.6 友元
友元函数
友元类
4.7 类的组合
4.8 共享数据的保护
五、继承与派生
5.1 继承与派生的概念
基类成员在派生类中的访问属性
派生类对基类成员的访问规则
5.2 派生类的构造函数和析构函数
5.3 调整基类成员在派生类中的访问属性的其他方法
5.4 多继承
5.5 虚基类
5.6 赋值兼容规则
六、多态性与虚函数
6.1 多态性概述
6.2 虚函数
6.3 虚析构函数
6.4 纯虚函数
6.5 抽象类
6.6 示例:利用多态计算面积
七、运算符重载
7.1 运算符重载概述
八、函数模板与类模板
8.1 模板的概念
8.2 函数模板
8.3 类模板
九、C++的输入和输出
9.1 C++为何建立自己的输入/输出系统
9.2 C++的流库及其基本结构
用于输入/输出的头文件
用于输入/输出的流类
9.3 预定义的流对象
9.4 输入/输出流的成员函数
9.5 预定义类型输入/输出的格式控制
9.6 文件的输入/输出
9.7 文件的打开与关闭
9.8 文本文件的读/写
9.9 二进制文件的读写
用get()函数和put()函数读/写二进制文件
用read()函数和write()函数读写二进制文件
检测文件结束
十、异常处理和命名空间
10.1 异常处理
10.2 命名空间和头文件命名规则
十一、STL标准模板库
11.1 Vector
11.2 list容器
11.3 stack
11.4 queue
11.5 优先队列priority_queue
11.6 双端队列deque
11.7 set
11.8 map
一、面向对象程序设计
面向对象程序设计(Object-Oriented Programming,OOP)是一种新的程序设计范型。程序设计范型是指设计程序的规范、模型和风格,它是一类程序设计语言的基础。
面向过程程序设计范型是使用较广泛的面向过程性语言,其主要特征是:程序由过程定义和过程调用组成(简单地说,过程就是程序执行某项操作的一段代码,函数就是最常用的过程)。
面向对象程序的基本元素是对象,面向对象程序的主要结构特点是:第一,程序一般由类的定义和类的使用两部分组成;第二,程序中的一切操作都是通过向对象发送消息来实现的,对象接收到消息后,启动有关方法完成相应的操作。
对象:描述其属性的数据以及对这些数据施加的一组操作封装在一起构成的统一体。对象可认为是数据+操作。
类:类是具有相同的数据和相同的操作的一组对象的集合。
消息传递:对象之间的交互。
**方法:**对象实现的行为称为方法。
面向对象程序设计的基本特征:抽象、封装、继承、多态。
二、C++基础
~
2.1 C++的产生和特点
C++是美国贝尔实验室的Bjarne Stroustrup博士在C语言的基础上,弥补了C语言存在的一些缺陷,增加了面向对象的特征,于1980年开发出来的一种面向过程性与面向对象性相结合的程序设计语言。最初他把这种新的语言称为“含类的C”,到1983年才取名为C++。
相比C语言,C++的主要特点是增加了面向对象机制。
~
2.2 一个简单的C++示例程序
详细创建步骤可参考博客【Visual Studio】 创建C/C++项目
#include <iostream> //编译预处理命令
using namespace std; //使用命名空间
int add(int a, int b); //函数原型说明
int main() //主函数
{
int x, y;
cout << "Enter two numbers: " << endl;
cin >> x;
cin >> y;
int sum = add(x, y);
cout << "The sum is : " << sum << '\n';
return 0;
}
int add(int a, int b) //定义add()函数,函数值为整型
{
return a + b;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
~
2.3 C++在非面向对象方面对C语言的扩充
输入和输出
int i;
float f;
cin >> i;
cout << f;
------------
scanf("%d", &i);
printf("%f", f);
----------------
连续读入
cin >> a >> b >> c;
1
2
3
4
5
6
7
8
9
10
cin
在默认情况下,运算符“>>”将跳过空白符,然后读入后面与变量类型相对应的值。因此,给一组变量输入值时可用空格符、回车符、制表符将输入的数据间隔开。
当输入字符串(即类型为string的变量)时,提取运算符“>>”的作用是跳过空白字符,读入后面的非空白字符,直到遇到另一个空白字符为止,并在串尾放一个字符串结束标志‘\0’。
~
C++允许在代码块中的任何地方声明局部变量。
const修饰符
在C语言中,习惯使用#define来定义常量,例如#define PI 3.14,C++提供了一种更灵活、更安全的方式来定义常量,即使用const修饰符来定义常量。例如const float PI = 3.14;
const可以与指针一起使用,它们的组合情况复杂,可归纳为3种:指向常量的指针、常指针和指向常量的常指针。
指向常量的指针:一个指向常量的指针变量。
const char* pc = "abcd";
该方法不允许改变指针所指的变量,即
pc[3] = ‘x'; 是错误的,
但是,由于pc是一个指向常量的普通指针变量,不是常指针,因此可以改变pc所指的地址,例如
pc = "ervfs";
该语句付给了指针另一个字符串的地址,改变了pc的值。
1
2
3
4
5
6
常指针:将指针变量所指的地址声明为常量
char* const pc = "abcd";
创建一个常指针,一个不能移动的固定指针,可更改内容,如
pc[3] = 'x';
但不能改变地址,如
pc = 'dsff'; 不合法
1
2
3
4
5
指向常量的常指针:这个指针所指的地址不能改变,它所指向的地址中的内容也不能改变。
const char* const pc = "abcd";
内容和地址均不能改变
1
2
说明:
如果用const定义整型常量,关键字可以省略。即 const in bufsize = 100 与 const bufsize = 100等价;
常量一旦被建立,在程序的任何地方都不能再更改。
与#define不同,const定义的常量可以有自己的数据类型。
函数参数也可以用const说明,用于保证实参在该函数内不被改动。
void型指针
void通常表示无值,但将void作为指针的类型时,它却表示不确定的类型。这种void型指针是一种通用型指针,也就是说任何类型的指针值都可以赋给void类型的指针变量。
需要指出的是,这里说void型指针是通用指针,是指它可以接受任何类型的指针的赋值,但对已获值的void型指针,对它进行再处理,如输出或者传递指针值时,则必须再进行显式类型转换,否则会出错。
void* pc;
int i = 123;
char c = 'a';
pc = &i;
cout << pc << endl; //输出指针地址006FF730
cout << *(int*)pc << endl; //输出值123
pc = &c;
cout << *(char*)pc << endl; //输出值a
1
2
3
4
5
6
7
8
内联函数
在函数名前冠以关键字inline,该函数就被声明为内联函数。每当程序中出现对该函数的调用时,C++编译器使用函数体中的代码插入到调用该函数的语句之处,同时使用实参代替形参,以便在程序运行时不再进行函数调用。引入内联函数主要是为了消除调用函数时的系统开销,以提高运行速度。
说明:
内联函数在第一次被调用之前必须进行完整的定义,否则编译器将无法知道应该插入什么代码
在内联函数体内一般不能含有复杂的控制语句,如for语句和switch语句等
使用内联函数是一种空间换时间的措施,若内联函数较长,较复杂且调用较为频繁时不建议使用
#include <iostream>
using namespace std;
inline double circle(double r) //内联函数
{
double PI = 3.14;
return PI * r * r;
}
int main()
{
for (int i = 1; i <= 3; i++)
cout << "r = " << i << " area = " << circle(i) << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
使用内联函数替代宏定义,能消除宏定义的不安全性
带有默认参数值的函数
当进行函数调用时,编译器按从左到右的顺序将实参与形参结合,若未指定足够的实参,则编译器按顺序用函数原型中的默认值来补足所缺少的实参。
void init(int x = 5, int y = 10);
init (100, 19); // 100 , 19
init(25); // 25, 10
init(); // 5, 10
1
2
3
4
在函数原型中,所有取默认值的参数都必须出现在不取默认值的参数的右边。
如 int fun(int a, int b, int c = 111);
1
在函数调用时,若某个参数省略,则其后的参数皆应省略而采取默认值。不允许某个参数省略后,再给其后的参数指定参数值。
函数重载
在C++中,用户可以重载函数。这意味着,在同一作用域内,只要函数参数的类型不同,或者参数的个数不同,或者二者兼而有之,两个或者两个以上的函数可以使用相同的函数名。
#include <iostream>
using namespace std;
int add(int x, int y)
{
return x + y;
}
double add(double x, double y)
{
return x + y;
}
int add(int x, int y, int z)
{
return x + y + z;
}
int main()
{
int a = 3, b = 5, c = 7;
double x = 10.334, y = 8.9003;
cout << add(a, b) << endl;
cout << add(x, y) << endl;
cout << add(a, b, c) << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
说明:
调用重载函数时,函数返回值类型不在参数匹配检查之列。因此,若两个函数的参数个数和类型都相同,而只有返回值类型不同,则不允许重载。
int mul(int x, int y);
double mul(int x, int y);
1
2
函数的重载与带默认值的函数一起使用时,有可能引起二义性。
void Drawcircle(int r = 0, int x = 0, int y = 0);
void Drawcircle(int r);
Drawcircle(20);
1
2
3
在调用函数时,如果给出的实参和形参类型不相符,C++的编译器会自动地做类型转换工作。如果转换成功,则程序继续执行,在这种情况下,有可能产生不可识别的错误。
void f_a(int x);
void f_a(long x);
f_a(20.83);
1
2
3
作用域标识符"::"
通常情况下,如果有两个同名变量,一个是全局的,另一个是局部的,那么局部变量在其作用域内具有较高的优先权,它将屏蔽全局变量。
如果希望在局部变量的作用域内使用同名的全局变量,可以在该变量前加上“::”,此时::value代表全局变量value,“::”称为作用域标识符。
#include <iostream>
using namespace std;
int value; //定义全局变量value
int main()
{
int value; //定义局部变量value
value = 100;
::value = 1000;
cout << "local value : " << value << endl;
cout << "global value : " << ::value << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
强制类型转换
可用强制类型转换将不同类型的数据进行转换。例如,要把一个整型数(int)转换为双精度型数(double),可使用如下的格式:
int i = 10;
double x = (double)i;
或
int i = 10;
double x = double(i);
1
2
3
4
5
以上两种方法C++都能接受,建议使用后一种方法。
new和delete运算符
程序运行时,计算机的内存被分为4个区:程序代码区、全局数据区、堆和栈。其中,堆可由用户分配和释放。C语言中使用函数malloc()和free()来进行动态内存管理。C++则提供了运算符new和delete来做同样的工作,而且后者比前者性能更优越,使用更灵活方便。
指针变量名 = new 类型
int *p;
p = new int;
delete 指针变量名
delete p;
1
2
3
4
5
下面对new和delete的使用再做一下几点说明:
用运算符new分配的空间,使用结束后应该用也只能用delete显式地释放,否则这部分空间将不能回收而变成死空间。
在使用运算符new动态分配内存时,如果没有足够的内存满足分配要求,new将返回空指针(NULL)。
使用运算符new可以为数组动态分配内存空间,这时需要在类型后面加上数组大小。
指针变量名 = new 类型名[下标表达式];
int *p = new int[10];
1
2
释放动态分配的数组存储区时,可使用delete运算符。
delete []指针变量名;
delete p;
1
2
new 可在为简单变量分配空间的同时,进行初始化
指针变量名 = new 类型名(初值);
int *p;
p = new int(99);
···
delete p;
1
2
3
4
5
引用
引用(reference)是C++对C的一个重要扩充。变量的引用就是变量的别名,因此引用又称别名。
类型 &引用名 = 已定义的变量名
1
引用与其所代表的变量共享同一内存单元,系统并不为引用另外分配存储空间。实际上,编译系统使引用和其代表的变量具有相同的地址。
#include <iostream>
using namespace std;
int main()
{
int i = 10;
int &j = i;
cout << "i = " << i << " j = " << j << endl;
cout << "i的地址为 " << &i << endl;
cout << "j的地址为 " << &j << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
上面代码输出i和j的值相同,地址也相同。
引用并不是一种独立的数据类型,它必须与某一种类型的变量相联系。在声明引用时,必须立即对它进行初始化,不能声明完成后再赋值。
为引用提供的初始值,可以是一个变量或者另一个引用。
指针是通过地址间接访问某个变量,而引用则是通过别名直接访问某个变量。
引用作为函数参数、使用引用返回函数值
#include <iostream>
using namespace std;
void swap(int &a, int &b)
{
int t = a;
a = b;
b = t;
}
int a[] = {1, 3, 5, 7, 9};
int& index(int i)
{
return a[i];
}
int main()
{
int a = 5, b = 10;
//交换数字a和b
swap(a, b);
cout << "a = " << a << " b = " << b << endl;
cout << index(2) << endl; //等价于输出元素a[2]的值
index(2) = 100; //等价于将a[2]的值赋为100;
cout << index(2) << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
对引用的进一步说明
不允许建立void类型的引用
不能建立引用的数组
不能建立引用的引用。不能建立指向引用的指针。引用本身不是一种数据类型,所以没有引用的引用,也没有引用的指针。
可以将引用的地址赋值给一个指针,此时指针指向的是原来的变量。
可以用const对引用加以限定,不允许改变该引用的值,但是它不阻止引用所代表的变量的值。
三、类和对象(一)
~
3.1 类的构成
类声明中的内容包括数据和函数,分别称为数据成员和成员函数。按访问权限划分,数据成员和成员函数又可分为共有、保护和私有3种。
class 类名{
public:
公有数据成员;
公有成员函数;
protected:
保护数据成员;
保护成员函数;
private:
私有数据成员;
私有成员函数;
};
1
2
3
4
5
6
7
8
9
10
11
如成绩类
class Score{
public:
void setScore(int m, int f);
void showScore();
private:
int mid_exam;
int fin_exam;
};
1
2
3
4
5
6
7
8
对一个具体的类来讲,类声明格式中的3个部分并非一定要全有,但至少要有其中的一个部分。一般情况下,一个类的数据成员应该声明为私有成员,成员函数声明为共有成员。这样,内部的数据整个隐蔽在类中,在类的外部根本就无法看到,使数据得到有效的保护,也不会对该类以外的其余部分造成影响,程序之间的相互作用就被降低到最小。
类声明中的关键字private、protected、public可以任意顺序出现。
若私有部分处于类的第一部分时,关键字private可以省略。这样,如果一个类体中没有一个访问权限关键字,则其中的数据成员和成员函数都默认为私有的。
不能在类声明中给数据成员赋初值。
~
3.2 成员函数的定义
普通成员函数的定义
在类的声明中只给出成员函数的原型,而成员函数的定义写在类的外部。这种成员函数在类外定义的一般形式是:
返回值类型 类名::成员函数名(参数表){ 函数体}
1
例如,表示分数的类Score可声明如下:
class Score{
public:
void setScore(int m, int f);
void showScore();
private:
int mid_exam;
int fin_exam;
};
void Score::setScore(int m, int f)
{
mid_exam = m;
fin_exam = f;
}
void Score::showScore()
{
cout << "期中成绩: " << mid_exam << endl;
cout << "期末成绩:" << fin_exam << endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
内联成员函数的定义
隐式声明:将成员函数直接定义在类的内部
class Score{
public:
void setScore(int m, int f)
{
mid_exam = m;
fin_exam = f;
}
void showScore()
{
cout << "期中成绩: " << mid_exam << endl;
cout << "期末成绩:" << fin_exam << endl;
}
private:
int mid_exam;
int fin_exam;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
显式声明:在类声明中只给出成员函数的原型,而将成员函数的定义放在类的外部。
class Score{
public:
inline void setScore(int m, int f);
inline void showScore();
private:
int mid_exam;
int fin_exam;
};
inline void Score::setScore(int m, int f)
{
mid_exam = m;
fin_exam = f;
}
inline void Score::showScore()
{
cout << "期中成绩: " << mid_exam << endl;
cout << "期末成绩:" << fin_exam << endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
说明:在类中,使用inline定义内联函数时,必须将类的声明和内联成员函数的定义都放在同一个文件(或同一个头文件)中,否则编译时无法进行代码置换。
~
3.3 对象的定义和使用
通常把具有共同属性和行为的事物所构成的集合称为类。
类的对象可以看成该类类型的一个实例,定义一个对象和定义一个一般变量相似。
对象的定义
在声明类的同时,直接定义对象
class Score{
public:
void setScore(int m, int f);
void showScore();
private:
int mid_exam;
int fin_exam;
}op1, op2;
1
2
3
4
5
6
7
8
声明了类之后,在使用时再定义对象
Score op1, op2;
1
对象中成员的访问
对象名.数据成员名对象名.成员函数名[(参数表)]op1.setScore(89, 99);
op1.showScore();
1
2
说明:
在类的内部所有成员之间都可以通过成员函数直接访问,但是类的外部不能访问对象的私有成员。
在定义对象时,若定义的是指向此对象的指针变量,则访问此对象的成员时,不能用“.”操作符,而应该使用“->“操作符。如
Score op, *sc;
sc = &op;
sc->setScore(99, 100);
op.showScore();
1
2
3
4
类的作用域和类成员的访问属性
私有成员只能被类中的成员函数访问,不能在类的外部,通过类的对象进行访问。
一般来说,公有成员是类的对外接口,而私有成员是类的内部数据和内部实现,不希望外界访问。将类的成员划分为不同的访问级别有两个好处:一是信息隐蔽,即实现封装,将类的内部数据与内部实现和外部接口分开,这样使该类的外部程序不需要了解类的详细实现;二是数据保护,即将类的重要信息保护起来,以免其他程序进行不恰当的修改。
对象赋值语句
Score op1, op2;
op1.setScore(99, 100);
op2 = op1;
op2.showScore();
1
2
3
4
~
3.4 构造函数与析构函数
构造函数
构造函数是一种特殊的成员函数,它主要用于为对象分配空间,进行初始化。构造函数的名字必须与类名相同,而不能由用户任意命名。它可以有任意类型的参数,但不能具有返回值。它不需要用户