大家好,我是阿秀。
最近发现一篇两万五千字的C++基础知识大汇总,可把我高兴坏了。
白嫖,必须白嫖!
感谢亮哥的整理和手机
一 、从”hello world“ 入门C++!
C++总览简介
C++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程。
C++ 被认为是一种中级语言,它综合了高级语言和低级语言的特点。
C++ 是由 Bjarne Stroustrup 于 1979 年在新泽西州美利山贝尔实验室开始设计开发的。C++ 进一步扩充和完善了 C 语言,最初命名为带类的C,后来在 1983 年更名为 C++。
C++ 是 C 的一个超集,事实上,任何合法的 C 程序都是合法的 C++ 程序。
注意:使用静态类型的编程语言是在编译时执行类型检查,而不是在运行时执行类型检查。
面向对象程序设计
c++最大的亮点就是面向对象程序设计理念的运用。包括面向对象开发的四大特性:
- 封装
- 抽象
- 继承
- 多态
C++的组成部分
标准的 C++ 由三个重要部分组成:
- 核心语言,提供了所有构件块,包括变量、数据类型和常量,等等。
- C++ 标准库,提供了大量的函数,用于操作文件、字符串等。
- 标准模板库(STL),提供了大量的方法,用于操作数据结构等。
C++的标准
对一门编程语言来说,遵循统一的标准是必需的。下面的表格列出了c++标准的发展历史。
发布时间 | 文档 | 通称 | 备注 |
2015 | ISO/IEC TS 19570:2015 | - | 用于并行计算的扩展 |
2015 | ISO/IEC TS 18822:2015 | - | 文件系统 |
2014 | ISO/IEC 14882:2014 | C++14 | 第四个C++标准 |
2011 | ISO/IEC TR 24733:2011 | - | 十进制浮点数扩展 |
2011 | ISO/IEC 14882:2011 | C++11 | 第三个C++标准 |
2010 | ISO/IEC TR 29124:2010 | - | 数学函数扩展 |
2007 | ISO/IEC TR 19768:2007 | C++TR1 | C++技术报告:库扩展 |
2006 | ISO/IEC TR 18015:2006 | - | C++性能技术报告 |
2003 | ISO/IEC 14882:2003 | C++03 | 第二个C++标准 |
C++ 程序结构
让我们看一段简单的代码,可以输出单词 Hello World。
#include <iostream> using namespace std; // main() 是程序开始执行的地方 int main() { cout << "Hello World"; // 输出 Hello World return 0; }
让我们一起来研究一下上面的代码结构:
C++ 语言定义了一些头文件,这些头文件包含了程序中必需的或有用的信息。上面这段程序中,包含了头文件 。
下一行 using namespace std; 告诉编译器使用 std 命名空间。命名空间是 C++ 中一个相对新的概念。
下一行 // main() 是程序开始执行的地方 是一个单行注释。单行注释以 // 开头,在行末结束。
下一行 int main() 是主函数,程序从这里开始执行。
下一行 cout << "Hello World"; 会在屏幕上显示消息 "Hello World"。
下一行 return 0; 终止 main( )函数,并向调用进程返回值 0。
C++ 中的分号 & 语句块
在 C++ 中,分号是语句结束符。也就是说,每个语句必须以分号结束。它表明一个逻辑实体的结束。
例如:下面就一共有两条语句。
x = y; y = x+1;
语句块是一组使用大括号括起来的按逻辑连接的语句。例如:
{ cout << "Hello World"; // 输出 Hello World return 0; }
C++ 关键字
下表列出了 C++ 中的保留字。这些保留字不能作为常量名、变量名或其他标识符名称。
asm | else | new | this |
auto | enum | operator | throw |
bool | explicit | private | true |
break | export | protected | try |
case | extern | public | typedef |
catch | false | register | typeid |
char | float | reinterpret_cast | typename |
class | for | return | union |
const | friend | short | unsigned |
const_cast | goto | signed | using |
continue | if | sizeof | virtual |
default | inline | static | void |
delete | int | static_cast | volatile |
do | long | struct | wchar_t |
double | mutable | switch | while |
dynamic_cast | namespace | template |
C++ 标识符
C++ 标识符是用来标识变量、函数、类、模块,或任何其他用户自定义项目的名称。一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9),不允许改变标识符组成规则。
下面列出几个有效的标识符:
test _temp
二 、 c++基本数据类型及流控制语句详解
C++ 数组
C++ 支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 numbers[0]、numbers[1]、...、numbers[99] 来代表一个个单独的变量。数组中的特定元素可以通过索引访问。
所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。
声明数组
在 C++ 中要声明一个数组,需要指定元素的类型和元素的数量,如下所示:
type arrayName [ arraySize ];
这叫做一维数组。arraySize 必须是一个大于零的整数常量,type 可以是任意有效的 C++ 数据类型。例如,要声明一个类型为 double 的包含 10 个元素的数组 balance,声明语句如下:
double balance[10];
现在 balance 是一个可用的数组,可以容纳 10 个类型为 double 的数字。
初始化数组
在 C++ 中,可以逐个初始化数组,也可以使用一个初始化语句,如下所示:
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
大括号 { } 之间的值的数目不能大于我们在数组声明时在方括号 [ ] 中指定的元素数目。
如果省略掉了数组的大小,数组的大小则为初始化时元素的个数。因此,如果:
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
将创建一个数组,它与前一个实例中所创建的数组是完全相同的。
下面是一个为数组中某个元素赋值的实例:
balance[4] = 50.0;
上述的语句把数组中第五个元素的值赋为 50.0。所有的数组都是以 0 作为它们第一个元素的索引,也被称为基索引,数组的最后一个索引是数组的总大小减去 1。以下是上面所讨论的数组的的图形表示:
访问数组元素
数组元素可以通过数组名称加索引进行访问。元素的索引是放在方括号内,跟在数组名称的后边。例如:
double salary = balance[9];
上面的语句将把数组中第 10 个元素的值赋给 salary 变量。下面的实例使用了上述的三个概念,即,声明数组、数组赋值、访问数组:
#include <iostream> using namespace std; #include <iomanip> using std::setw; int main () { int n[ 10 ]; // n 是一个包含 10 个整数的数组 // 初始化数组元素 for ( int i = 0; i < 10; i++ ) { n[ i ] = i + 100; // 设置元素 i 为 i + 100 } cout << "Element" << setw( 13 ) << "Value" << endl; // 输出数组中每个元素的值 for ( int j = 0; j < 10; j++ ) { cout << setw( 7 )<< j << setw( 13 ) << n[ j ] << endl; } return 0; }
C++ 字符串
C++ 提供了以下两种类型的字符串表示形式:
- C 风格字符串
- C++ 引入的 string 类类型
C 风格字符串
C 风格的字符串起源于 C 语言,并在 C++ 中继续得到支持。字符串实际上是使用 null 字符 '\0' 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。
下面的声明和初始化创建了一个 "Hello" 字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词 "Hello" 的字符数多一个。
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
依据数组初始化规则,可以把上面的语句写成以下语句:
char greeting[] = "Hello";
以下是 C/C++ 中定义的字符串的内存表示:
其实,不需要把 null 字符放在字符串常量的末尾。C++ 编译器会在初始化数组时,自动把 '\0' 放在字符串的末尾。让我们尝试输出上面的字符串:
实例
#include <iostream> using namespace std; int main (){ char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; cout << "Greeting message: "; cout << greeting << endl; return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
Greeting message: Hello
C++ 中有大量的函数用来操作以 null 结尾的字符串:supports a wide range of functions that manipulate null-terminated strings:
序号 | 函数 & 目的 |
1 |
strcpy(s1, s2); 复制字符串 s2 到字符串 s1。 |
2 |
strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。 |
3 |
strlen(s1); 返回字符串 s1 的长度。 |
4 |
strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回值小于 0;如果 s1>s2 则返回值大于 0。 |
5 |
strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 |
6 |
strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 |
下面的实例使用了上述的一些函数:
实例
#include <iostream> #include <cstring> using namespace std; int main () { char str1[11] = "Hello"; char str2[11] = "World"; char str3[11]; int len ; // 复制 str1 到 str3 strcpy( str3, str1); cout << "strcpy( str3, str1) : " << str3 << endl; // 连接 str1 和 str2 strcat( str1, str2); cout << "strcat( str1, str2): " << str1 << endl; // 连接后,str1 的总长度 len = strlen(str1); cout << "strlen(str1) : " << len << endl; return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
strcpy( str3, str1) : Hellostrcat( str1, str2): HelloWorldstrlen(str1) : 10C++ 中的 String 类
C++ 中的 string 类
C++ 标准库提供了 string 类类型,支持上述所有的操作,另外还增加了其他更多的功能。现在让我们先来看看下面这个实例:
实例
#include <iostream> #include <string> using namespace std; int main () { string str1 = "Hello"; string str2 = "World"; string str3; int len ; // 复制 str1 到 str3 str3 = str1; cout << "str3 : " << str3 << endl; // 连接 str1 和 str2 str3 = str1 + str2; cout << "str1 + str2 : " << str3 << endl; // 连接后,str3 的总长度 len = str3.size(); cout << "str3.size() : " << len << endl; return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
str3 : Hello str1 + str2 : HelloWorld str3.size() : 10
C++ 循环
有的时候,可能需要多次执行同一块代码。一般情况下,语句是顺序执行的:函数中的第一个语句先执行,接着是第二个语句,依此类推。
编程语言提供了允许更为复杂的执行路径的多种控制结构。
循环语句允许我们多次执行一个语句或语句组,下面是大多数编程语言中循环语句的一般形式:
循环类型
C++ 编程语言提供了以下几种循环类型。
循环类型 | 描述 |
while 循环 |
当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。 |
for 循环 |
多次执行一个语句序列,简化管理循环变量的代码。 |
do...while 循环 |
除了它是在循环主体结尾测试条件外,其他与 while 语句类似。 |
嵌套循环 |
可以在 while、for 或 do..while 循环内使用一个或多个循环。 |
循环控制语句
循环控制语句更改执行的正常序列。当执行离开一个范围时,所有在该范围中创建的自动对象都会被销毁。
C++ 提供了下列的控制语句。
控制语句 | 描述 |
break 语句 |
终止 loop 或 switch 语句,程序流将继续执行紧接着 loop 或 switch 的下一条语句。 |
continue 语句 |
引起循环跳过主体的剩余部分,立即重新开始测试条件。 |
goto 语句 |
将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。 |
无限循环
如果条件永远不为假,则循环将变成无限循环。for 循环在传统意义上可用于实现无限循环。由于构成循环的三个表达式中任何一个都不是必需的,可以将某些条件表达式留空来构成一个无限循环。
实例
#include <iostream> using namespace std; int main () { for( ; ; ) { printf("This loop will run forever.\n"); } return 0; }
当条件表达式不存在时,它被假设为真。也可以设置一个初始值和增量表达式,但是一般情况下,C++ 程序员偏向于使用 for(;;) 结构来表示一个无限循环。
注意:可以按 Ctrl + C 键终止一个无限循环。
C++ 判断
判断结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的)。
下面是大多数编程语言中典型的判断结构的一般形式:
判断语句
C++ 编程语言提供了以下类型的判断语句。
语句 | 描述 |
if 语句 |
一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。 |
if...else 语句 |
一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。 |
嵌套 if 语句 |
可以在一个 if 或 else if 语句内使用另一个 if 或 else if 语句。 |
switch 语句 |
一个 switch 语句允许测试一个变量等于多个值时的情况。 |
嵌套 switch 语句 |
可以在一个 switch 语句内使用另一个 switch 语句。 |
三 、 c++进阶 基本输入输出、指针、引用
C++ 基本的输入输出
今天我们来熟悉一下C++ 编程中最基本和最常见的 I/O 操作。
C++ 的 I/O 发生在流中,流是字节序列。如果字节流是从设备(如键盘、磁盘驱动器、网络连接等)流向内存,这叫做输入操作。如果字节流是从内存流向设备(如显示屏、打印机、磁盘驱动器、网络连接等),这叫做输出操作。
I/O 库头文件
下列的头文件在 C++ 编程中很重要。
头文件 |
函数和描述 |
<iostream> |
该文件定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。 |
<iomanip> |
该文件通过所谓的参数化的流操纵器(比如 setw 和 setprecision),来声明对执行标准化 I/O 有用的服务。 |
<fstream> |
该文件为用户控制的文件处理声明服务。我们将在文件和流的相关章节讨论它的细节。 |
标准输出流(cout)
预定义的对象 cout 是 iostream 类的一个实例。cout 对象"连接"到标准输出设备,通常是显示屏。cout 是与流插入运算符 << 结合使用的,如下所示:
实例
#include <iostream> using namespace std; int main( ) { char str[] = "Hello C++"; cout << "Value of str is : " << str << endl; }
当上面的代码被编译和执行时,它会产生下列结果:
Value of str is : Hello C++
C++ 编译器根据要输出变量的数据类型,选择合适的流插入运算符来显示值。<< 运算符被重载来输出内置类型(整型、浮点型、double 型、字符串和指针)的数据项。
流插入运算符 << 在一个语句中可以多次使用,如上面实例中所示,endl 用于在行末添加一个换行符。
标准输入流(cin)
预定义的对象 cin 是 iostream 类的一个实例。cin 对象附属到标准输入设备,通常是键盘。cin 是与流提取运算符 >> 结合使用的,如下所示:
实例
#include <iostream> using namespace std; int main( ) { char name[50]; cout << "请输入您的名称:"; cin >> name; cout << "您的名称是:" << name << endl; }
当上面的代码被编译和执行时,它会提示用户输入名称。当用户输入一个值,并按回车键,就会看到下列结果:
请输入您的名称:cplusplus 您的名称是:cplusplus
C++ 编译器根据要输入值的数据类型,选择合适的流提取运算符来提取值,并把它存储在给定的变量中。
流提取运算符 >> 在一个语句中可以多次使用,如果要求输入多个数据,可以使用如下语句:
cin >> name >> age;
这相当于下面两个语句:
cin >> name; cin >> age;
标准错误流(cerr)
预定义的对象 cerr 是 iostream 类的一个实例。cerr 对象附属到标准错误设备,通常也是显示屏,但是 cerr 对象是非缓冲的,且每个流插入到 cerr 都会立即输出。
cerr 也是与流插入运算符 << 结合使用的,如下所示:
实例
#include <iostream> using namespace std; int main( ) { char str[] = "Unable to read...."; cerr << "Error message : " << str << endl; }
当上面的代码被编译和执行时,它会产生下列结果:
Error message : Unable to read....
标准日志流(clog)
预定义的对象 clog 是 iostream 类的一个实例。clog 对象附属到标准错误设备,通常也是显示屏,但是 clog 对象是缓冲的。这意味着每个流插入到 clog 都会先存储在缓冲在,直到缓冲填满或者缓冲区刷新时才会输出。
clog 也是与流插入运算符 << 结合使用的,如下所示:
实例
#include <iostream> using namespace std; int main( ) { char str[] = "Unable to read...."; clog << "Error message : " << str << endl; }
当上面的代码被编译和执行时,它会产生下列结果:
Error message : Unable to read....
通过这些小实例,我们无法区分 cout、cerr 和 clog 的差异,但在编写和执行大型程序时,它们之间的差异就变得非常明显。所以良好的编程实践告诉我们,使用 cerr 流来显示错误消息,而其他的日志消息则使用 clog 流来输出。
C++ 指针
学习 C++ 的指针既简单又有趣。通过指针,可以简化一些 C++ 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C++ 程序员,学习指针是很有必要的。
每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。请看下面的实例,它将输出定义的变量地址:
实例
#include <iostream> using namespace std; int main () { int var1; char var2[10]; cout << "var1 变量的地址:"; cout << &var1 << endl; cout << "var2 变量的地址:"; cout << &var2 << endl; return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
var1 变量的地址:0xbfebd5c0 var2 变量的地址:0xbfebd5b6
通过上面的实例,我们了解了什么是内存地址以及如何访问它。接下来让我们看看什么是指针。
什么是指针?
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type *var-name;
在这里,type 是指针的基类型,它必须是一个有效的 C++ 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:
int *ip; /* 一个整型的指针 */ double *dp; /* 一个 double 型的指针 */ float *fp; /* 一个浮点型的指针 */ char *ch; /* 一个字符型的指针 */
所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
C++ 中使用指针
使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 *来返回位于操作数所指定地址的变量的值。下面的实例涉及到了这些操作:
实例
#include <iostream> using namespace std; int main () { int var = 20; // 实际变量的声明 int *ip; // 指针变量的声明 ip = &var; // 在指针变量中存储 var 的地址 cout << "Value of var variable: "; cout << var << endl; // 输出在指针变量中存储的地址 cout << "Address stored in ip variable: "; cout << ip << endl; // 访问指针中地址的值 cout << "Value of *ip variable: "; cout << *ip << endl; return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
Value of var variable: 20 Address stored in ip variable: 0xbfc601ac Value of *ip variable: 20
C++ 引用
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
C++ 引用 vs 指针
引用很容易与指针混淆,它们之间有三个主要的不同:
- 不存在空引用。引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
C++ 中创建引用
试想变量名称是变量附属在内存位置中的标签,可以把引用当成是变量附属在内存位置中的第二个标签。因此,可以通过原始变量名称或引用来访问变量的内容。例如:
int i = 17;
我们可以为 i 声明引用变量,如下所示:
int& r = i; double& s = d;
在这些声明中,& 读作引用。因此,第一个声明可以读作 "r 是一个初始化为 i 的整型引用",第二个声明可以读作 "s 是一个初始化为 d 的 double 型引用"。下面的实例使用了 int 和 double 引用:
实例
#include <iostream> using namespace std; int main () { // 声明简单的变量 int i; double d; // 声明引用变量 int& r = i; double& s = d; i = 5; cout << "Value of i : " << i << endl; cout << "Value of i reference : " << r << endl; d = 11.7; cout << "Value of d : " << d << endl; cout << "Value of d reference : " << s << endl; return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
Value of i : 5 Value of i reference : 5 Value of d : 11.7 Value of d reference : 11.7
引用通常用于函数参数列表和函数返回值。下面列出了 C++ 程序员必须清楚的两个与 C++ 引用相关的重要概念:
概念 | 描述 |
把引用作为参数 |
C++ 支持把引用作为参数传给函数,这比传一般的参数更安全。 |
把引用作为返回值 |
可以从 C++ 函数中返回引用,就像返回其他数据类型一样。 |
四、 C++ 类与对象初探:继承和重载
C++ 类 & 对象
C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。
类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中被称为类的成员。
C++ 类定义
定义一个类,本质上是定义一个数据类型的蓝图。这实际上并没有定义任何数据,但它定义了类的名称意味着什么,也就是说,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。
类定义是以关键字 class 开头,后跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表。例如,我们使用关键字 class 定义 Box 数据类型,如下所示:
class Box { public: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度 };
关键字 public 确定了类成员的访问属性。在类对象作用域内,公共成员在类的外部是可访问的。也可以指定类的成员为 private 或 protected。
定义 C++ 对象
类提供了对象的蓝图,所以基本上,对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。下面的语句声明了类 Box 的两个对象:
Box Box1; // 声明 Box1,类型为 Box Box Box2; // 声明 Box2,类型为 Box对象 Box1 和 Box2 都有它们各自的数据成员。
访问数据成员
类的对象的公共数据成员可以使用直接成员访问运算符 (.) 来访问。为了更好地理解这些概念,让我们尝试一下下面的实例:
实例
#include <iostream> using namespace std; class Box { public: double length; // 长度 double breadth; // 宽度 double height; // 高度 }; int main( ) { Box Box1; // 声明 Box1,类型为 Box Box Box2; // 声明 Box2,类型为 Box double volume = 0.0; // 用于存储体积 // box 1 详述 Box1.height = 5.0; Box1.length = 6.0; Box1.breadth = 7.0; // box 2 详述 Box2.height = 10.0; Box2.length = 12.0; Box2.breadth = 13.0; // box 1 的体积 volume = Box1.height * Box1.length * Box1.breadth; cout << "Box1 的体积:" << volume <<endl; // box 2 的体积 volume = Box2.height * Box2.length * Box2.breadth; cout << "Box2 的体积:" << volume <<endl; return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
Box1 的体积:210 Box2 的体积:1560
需要注意的是,私有的成员和受保护的成员不能使用直接成员访问运算符 (.) 来直接访问。
C++ 继承
面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行时间的效果。
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
继承代表了 is a 关系。例如,哺乳动物是动物,狗是哺乳动物,因此,狗是动物,等等。
基类 & 派生类
一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:
class derived-class: access-specifier base-class
其中,访问修饰符 access-specifier 是 public、protected 或 private 其中的一个,base-class 是之前定义过的某个类的名称。如果未使用访问修饰符 access-specifier,则默认为 private。
假设有一个基类 Shape,Rectangle 是它的派生类,如下所示:
实例
#include <iostream> using namespace std; // 基类 class Shape { public: void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } protected: int width; int height; }; // 派生类 class Rectangle: public Shape { public: int getArea() { return (width * height); } }; int main(void) { Rectangle Rect; Rect.setWidth(5); Rect.setHeight(7); // 输出对象的面积 cout << "Total area: " << Rect.getArea() << endl; return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
Total area: 35
访问控制和继承
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
我们可以根据访问权限总结出不同的访问类型,如下所示:
访问 | public | protected | private |
同一个类 |
yes |
yes |
yes |
派生类 |
yes |
yes |
no |
外部的类 |
yes |
no |
no |
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
继承类型
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。继承类型是通过上面讲解的访问修饰符 access-specifier 来指定的。
我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected):当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
多继承
多继承即一个子类可以有多个父类,它继承了多个父类的特性。
C++ 类可以从多个类继承成员,语法如下:
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,… { <派生类类体> };
其中,访问修饰符继承方式是 public、protected 或 private 其中的一个,用来修饰每个基类,各个基类之间用逗号分隔,如上所示。现在让我们一起看看下面的实例:
实例
#include <iostream> using namespace std; // 基类 Shape class Shape { public: void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } protected: int width; int height; }; // 基类 PaintCost class PaintCost { public: int getCost(int area) { return area * 70; } }; // 派生类 class Rectangle: public Shape, public PaintCost { public: int getArea() { return (width * height); } }; int main(void) { Rectangle Rect; int area; Rect.setWidth(5); Rect.setHeight(7); area = Rect.getArea(); // 输出对象的面积 cout << "Total area: " << Rect.getArea() << endl; // 输出总花费 cout << "Total paint cost: $" << Rect.getCost(area) << endl; return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
Total area: 35 Total paint cost: $2450