C++程序设计:自考知识点速记
todo
一、C++语言简介
1.1、C++语言的发展简史
1.2、C++语言的特点
1.3、C++语言的程序结构
二、面向对象的基本概念
2.1、结构化程序设计
2.2、面向对象程序设计的概念和特点
2.3、类的初步知识
2.4、类的示例程序剖析
2.5、访问对象的成员
通过对象访问成员变量的格式为:对象名.成员变量名
通过对象调用成员函数的格式为:对象名.成员函数名(参数表)
使用指针访问类成员的格式为:指针->成员名
2.6、类成员的可访问范围
public修饰的成员,可以在任何地方被访问
private修饰的成员,仅能在本类内被访问
protected修饰的成员,可以在本类及子类中被访问
没有使用访问修饰符的成员,默认是私有的,仅能在本类内被访问
设置私有成员的机制称为“隐藏”
“隐藏”的一个目的,就是强制对私有成员变量的访问,一定要通过公有成员函数进行
2.7、标识符的作用域与可见性
标识符的作用域,是指一个标识符在程序中的有效范围,即它在程序中的存在区域
标识符的可见性,是指在程序的哪个区域里可以使用
C++中标识符的作用域有函数原型作用域、局部作用域、类作用域(块作用域)和命名空间作用域
函数原型作用域,是最小的作用域。在声明函数原型时,形参的作用范围就是函数原型作用域
块,是指程序中使用相匹配的一对大括号括起来的一段程序
作用域局限在块内的称为局部作用域
对于在不同的作用域声明的标识符,可见性的一般原则为:
-
标识符要声明在前,引用在后
-
在同一个作用域中,不能声明同名的标识符
-
在没有互相包含关系的不同作用域中声明的同名标识符,互不影响
-
如果存在两个或多个具有包含关系的作用域,外层声明了一个标识符,而内层没有再次声明同名标识符,那么外层标识符在内层仍然可见;
如果在内层声明了同名标识符,则外层标识符在内层不可见,这时称内层标识符隐藏了外层同名标识符,这种机制称为隐藏规则
三、类和对象进阶
3.1、构造函数
类中的特殊成员函数,属于类的一部分
创建对象时,由系统自动调用
函数名与类名相同,没有返回值
可以有多个构造函数,即构造函数允许重载
在声明类的构造函数时,可以同时给出函数体,这样的构造函数称为内联函数
构造函数也可以在类外定义
构造函数的声明中,形参的个数可以为0,即参数表为空
当类中没有定义任何构造函数时,系统会自动添加一个参数表为空、函数体也为空的构造函数,称为默认构造函数
假设,类的成员变量是x1,x2,…,xn,则在类体外定义构造函数时,通常有如下3种形式:
第一种形式:
1 |
|
第二种形式:
1 |
|
第三种形式:
1 |
|
构造函数参数的排列顺序可以是任意的,只要能保证与类的成员变量相对应即可
可以使用默认参数为成员变量赋初值,也就是说,构造函数中实参的个数可以少于类的成员变量的个数
复制构造函数是构造函数的一种,也称为拷贝构造函数
其作用是使用一个已经存在的对象去初始化另一个正在创建的对象
复制构造函数只有一个参数,参数类型是本类的引用
3.2、析构函数
类中的特殊成员函数
函数名与类名相同,且需要在类名前加一个~
没有返回值
一个类有且仅有一个析构函数
如果程序中没有定义析构函数,则编译器自动生成默认的析构函数,其函数体为空
不可以有多个析构函数,即析构函数不允许重载
3.3、类的静态成员
使用static,可以说明静态变量
根据定义的位置不同,分为静态全局变量和静态局部变量
全局变量,是指在花括号外声明的变量,其作用域范围是全局可见的,即在整个项目文件内都有效
静态全局变量,是指使用static修饰的全局变量,其作用域有所限制,仅在定义该变量的源文件内有效,项目中的其他源文件不能使用它
局部变量,是指在块内定义的变量,其作用域范围,从定义处开始到本块结束处为止
静态局部变量,是指使用static修饰的局部变量,其特点是具有局部作用域,但却具有全局生存期
静态变量,存储在全局数据区,只执行一次初始化
如果程序未显式给出初始值,则相当于初始化未0
如果程序显式给出了初始值,则在变量所在块第一次执行时完成初始化
类的静态成员有两种:静态成员变量和静态成员函数
类的静态成员被类的所有对象共享,不论有多少对象存在,静态成员都只有一份保存在公有内存中
对于静态成员变量,各对象看到的值是一样的
静态成员变量本质上是全局变量
对于一个类来说,哪怕它一个对象都不存在,其静态成员变量也存在
静态成员变量本质上是全局函数,不需要作用在某个具体的对象上
3.4、变量及对象的生存期和作用域
变量的生存期,是指变量所占据的内存空间由分配到释放的时期
对象的生存期,是从调用构造函数开始,到调用完析构函数为止
作用域分为:
- 全局域,包括程序作用域、文件作用域
- 局部域,包括类作用域、函数作用域,块作用域和函数原型作用域等
程序作用域,也称为多文件作用域,属于程序作用域的有通过extern关键字进行说明的外部变量以及外部函数等
3.5、常量成员和常引用成员
由关键字const修饰的类成员变量称为类的常量成员变量
类的常量成员变量必须进行初始化,而且只能通过构造函数的成员初始化列表的方式进行
使用const修饰的函数称为常量函数
定义常量对象或常量成员变量的一般格式为:
1 |
|
定义常量函数的格式为:
1 |
|
3.6、成员对象和封闭类
如果类的成员变量是另一个类的对象,则该成员变量称为“成员对象”
这两个类是包含关系
包含成员对象的类叫作封闭类
在封闭类的构造函数中,添加初始化列表的格式如下:
1 |
|
3.7、友元
友元并不是面向对象的特征
是为了兼顾C语言的程序设计习惯与C++的信息隐藏特性而增加的功能
是一种类成员的访问权限
友元破坏了类的封装性和信息隐藏,但有助于数据共享,能够提高程序执行效率
友元使用关键字friend标识
在类定义中,当friend出现在函数说明语句的前面时,表示该函数为类的友元函数
一个函数可以是多个类的友元函数,一个类中也可以有多个友元函数
在类定义中,将一个全局函数声明为本类友元函数的格式如下:
1 |
|
当有某类A的定义后,将类A的成员函数说明为本类的友元函数的格式如下:
1 |
|
在类中,声明友元类的格式如下:
1 |
|
友元的关系是单向的
若说明类B是类A的友元类,不等于类A也是类B的友元类
友元函数不是类的成员函数,但可以访问类中的所有成员
不能把其他类的所有成员函数声明为友元函数
友元函数不受类中的访问权限关键字的限制,可以把它放在公有、私有、保护部分,其结果都是一样的
3.8、this指针
在C++中,当调用一个成员函数时,系统自动向它传递一个隐含的参数
该参数是一个指向调用该函数的对象的指针,称为this指针
this指针是C++实现封装的一种机制
它将对象和该对象调用的成员函数联系在一起,从外部来看,好像每一个对象都拥有自己的成员函数
四、运算符重载
4.1、运算符重载的概念
C++中的表达式由运算符和操作数按照规则构成
运算符重载,是指给已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时产生不同的行为
在C++中,不可以重载的运算符有:
- .
- .*
- ->*
- ::
- sizeof
- ?:
有两个运算符,系统提供了默认的重载版本,它们是:
- 赋值运算符=:系统默认重载为,对象成员变量的复制
- 地址运算符&:系统默认重载为,返回任何类对象的地址
运算符重载的实质是编写以运算符为名称的函数
当使用运算符表达式时,就被解释为对重载函数的调用
运算符重载函数的函数名,由关键字operator和其后要重载的运算符构成,其格式如下:
1 |
|
在重载运算符时,有如下规则:
- 重载后,运算符的含义,应该符合原有的用法习惯
- 不能改变运算符原有的语义。包括运算符的优先级和结合性
- 不能改变运算符操作数的个数和语法结构
- 不能创建新的运算符
- 运算符
()、[]、->和=
,只能重载为成员函数,不能重载为全局函数 - 不能改变运算符用于基本数据类型对象的含义
4.2、重载赋值运算符
赋值运算符=,只能重载为成员函数
同类对象之间可以通过赋值运算符互相赋值
如果没有重载赋值运算符,则=作用是将其右侧对象的值一一赋值给左侧对象,这相当于值的拷贝,称为“浅拷贝”
重载赋值运算符,常用于实现“深拷贝”
4.3、重载流提取运算符和流插入运算符
右移运算符>>可以和cin一起用于输入,常被称为流提取运算符
左移运算符<<可以和cout一起用于输出,常被称为流插入运算符
流是标准类库,用户程序只能继承不能修改
重载函数不能是流类库中的成员,必须重载为类的友元
4.4、重载强制类型转换运算符
在C++中,类型的名字(包括类的名字),其本身也是一种运算符,即强制类型转换运算符
强制类型转换运算符是单目运算符,也可以被重载,但只能重载为成员函数,不能重载为全局函数
经过适当重载后,“(类型名)对象”这个对对象进行强制类型转换的表达式就等价于“对象.operator类型名()”,即变成对运算符函数的调用
4.5、重载自增、自减运算符
自增、自减运算符都可以被重载,但有前置和后置之分
区分前置和后置的方式是,在参数表中增加一个不使用的int类型
前置,是参数个数正常的重载函数
后置,是多出一个参数的重载函数
五、类的继承与派生
5.1、类的继承与类的派生
通过已有的类建立新类的过程,叫作类的派生
原来的类称为基类,也称为父类或一般类;新类称为派生类,也称为子类或特殊类
派生类派生自基类,或继承于基类,也可以说基类派生了派生类
派生类可以再作为基类派生新的派生类
从基类派生派生类的一般格式为:
1 |
|
继承方式说明符,指明如何控制基类成员再派生类中的访问属性
继承方式通常有3种,分别是public(共有继承)、private(私有继承)和protected(保护继承)
一般情况下都使用public,protected和private方式很少用到
派生类对象占用的存储空间大小 = 基类成员变量占用的存储空间大小 + 派生类对象自身成员变量占用的存储空间大小
可以使用sizeof()函数计算对象占用的字节数
为类对象分配空间时,遵循字节对齐原则
空类的大小是1,这是一种特殊情况
对象占用的存储空间包含对象中各成员变量占用的存储空间
对象的大小与普通成员变量有关,与成员函数和类中的静态成员无关
即普通成员函数、静态成员函数、静态成员变量、静态常成员变量等均对类对象的大小没有影响
基类的成员和派生类中新增的成员都具有类作用域
但二者的作用范围不同,是相互包含的两个层,派生类在内层,基类在外层
单继承,是指派生类只有一个基类
多继承,是指派生类有多个基类
5.2、访问控制
不论是哪种继承方式,基类中的私有成员,在派生类中都无法访问。
采用公有继承,基类中的公有成员和保护成员,在派生类中的访问权限不变
采用保护继承,基类中的公有成员,在派生类中的访问权限变为保护权限,基类中的保护成员,在派生类中的访问权限不变
采用私有继承,基类中的公有成员和保护成员,在派生类中的访问权限都变为私有权限
类型兼容,也称为赋值兼容,是指在任何需要基类对象的地方,都可以使用公有派生类的对象来替代
在公有派生情况下,有3条类型兼容规则:
- 派生类对象,可以赋值给基类对象
- 派生类对象,可以用来初始化基类引用
- 派生类对象的地址,可以赋值给基类指针,即派生类指针可以赋值给基类指针
5.3、派生类的构造函数和析构函数
在继承中,构造函数的调用顺序是:先基类,后派生类
在继承中,析构函数的调用顺序是:先派生类,后基类
如果基类定义了有参构造函数,那么派生类也要定义有参构造函数
5.4、类之间的关系
使用已有类,编写新类的方式,有两种:继承和组合
如果一个类的成员变量是另一个类的对象,则该类为封闭类
定义封闭类,其构造函数的一般形式为:
1 |
|
5.5、多层次的派生
派生可以是多层次的
类之间的继承关系具有传递性
在定义派生类时,只需写直接基类,不用写间接基类
5.6、基类与派生类指针的互相转换
对于指针类型,可以使用基类指针指向派生类对象,也可以将派生类的指针直接赋值给基类指针
基类指针指向派生类对象时,不能通过基类指针访问基类中没有而仅在派生类中定义的成员
当派生类指针指向基类对象时,必须将基类对象进行强制类型转换,才能赋给派生类指针
六、多态与虚函数
6.1、多态的基本概念
多态,可以使同一个函数完成不同的功能
多态分为编译时多态和运行时多态
静态联编,是指在编译期间就可以确定函数的调用地址
动态联编,是指函数调用与代码入口地址的绑定需要在运行时刻才能确定
虚函数,是指类中的成员函数前加了virtual关键字
声明虚函数成员的一般格式如下:
1 |
|
6.2、多态实例
使用多态能够增强程序的可扩充性
使用多态能够起到精简代码的作用
6.3、多态的使用
类的成员函数之间是可以相互调用的
在普通成员函数(静态成员函数、构造函数和析构函数除外)中调用其他虚成员函数也是允许的,并且是多态的
实现多态时,必须满足的条件是:使用基类指针或引用,调用基类中声明的虚函数
6.4、虚析构函数
声明虚析构函数的格式为:virtual ~类名();
虚析构函数没有返回值类型,没有参数
如果一个类的析构函数是虚函数,则由它派生的所有子类的析构函数也是虚析构函数
6.5、纯虚函数和抽象类
纯虚函数,是指基类中的虚函数,没有具体的定义,而由各派生类根据实际需要给出各自的定义
声明纯虚函数的一般格式为:virtual 函数类型 函数名(参数表)=0;
包含纯虚函数的类称为抽象类
七、输入/输出流
7.1、流类简介
在C++的标准类库中,将与数据输入/输出相关的类统称为“流类”
istream类提供了流的大部分输入操作,对系统预定义的所有输入流,都重载了流提取运算符>>
ostream类提供了流的大部分输出操作,对系统预定义的所有输出流,都重载了流插入运算符<<
istream类和ostream类共同派生了iostream类
在流类中,常见的头文件有3个:iostream、fstream和iomanip。其中:
- 头文件iostream,包含操作所有输入/输出流所需的基本信息
- 头文件fstream,包含处理文件的有关信息,提供建立文件、读写文件的各种操作接口
- 头文件iomanip,包含格式I/O的有参流操纵符
7.2、标准流对象
标准流对象,是C++为用户提供的,用于外设与内存之间的通信,对数据进行解释和传输,提供必要的数据缓冲等
C++在头文件iostream中,为用户预定义了4个标准流对象,分别是:
- cin(标准输入流):与标准输入设备(键盘)相关联,用于读取数据,可以被重定向为从文件中读取数据
- cout(标准输出流):与标准输出设备(显示器)相关联,用于输出数据,可以被重定向为向文件里写入数据
- cerr(无缓冲的错误输出流):与标准错误信息输出设备(显示器)相关联,用于输出错误信息,不能被重定向(无缓冲)
- clog(有缓冲的错误输出流):与标准错误信息输出设备(显示器)相关联,用于输出错误信息,不能被重定向(有缓冲)
所谓重定向,就是改变默认的输入来源,或改变默认的输出目的地
7.3、控制I/O格式
C++进行I/O格式控制的方式,一般有:使用流操纵符、设置标志字和调用成员函数
为了满足不同用户对数据输入/输出格式的要求,C++提供了setiosflags()函数,设置标志字,进行格式控制
setiosflags()是有参数的操纵符,在头文件iostream中,用来设置指定的标志,函数的参数为流的格式标志位
7.4、调用cout的成员函数
ostream类提供了在cout中控制输出格式的成员函数,常见的用于控制格式的成员函数原型如下:
-
设置和返回标志字:
long flags(long lFlags);
long flags() const;
-
设置标志位:
long setf(long lFlags);
long setf(long lFlags, long lMask);
-
消除标志位:
long unsetf(long lFlags);
-
设置和返回输出宽度:
int width(int nw);
int width() const;
-
设置填充字符:
char fill(char cFill);
char fill() const;
-
设置数据显示精度:
int precision(int np);
int precision() const;
ostream类有一些输出流的成员函数,其原型如下:
- 字符插入:
ostream& put(char c);
- 数据块插入:
ostream& write(const char *pch, int nCount);
7.5、调用cin的成员函数
get()函数从输入流中读入一个字符(包括空白字符),返回值就是该字符的ASCII码
getline()函数从输入流中的当前字符开始读取bufSize-1个字符到缓冲区buf中,或读到‘\n’为止
eof()函数用于判断输入流是否已经结束
ignore()函数用于跳过输入流中的n个字符,或跳过delim及其之前的所有字符
peek()函数返回输入流中的当前字符,但是并不会将该字符从输入流中取走
八、文件操作
8.1、文件基本概念和文件流类
在计算机中,通常将一些内容相关的信息组织起来,保存在一起并用一个名字标识,这就是文件
根据文件的编码方式不同,文件可分为文本文件和二进制文件
根据文件的存取方式不同,文件可分为顺序存取文件和随机存取文件
流是一个逻辑概念,是对所有外部设备的逻辑抽象
C++标准库中有3个流类可以用于文件操作:
- ifstream
- ofstream
- fstream
8.2、打开和关闭文件
在对文件进行读写操作之前,先要打开文件。打开文件有两个目的:
- 一是,建立关联
- 二是,指明文件的使用方式和文件格式
打开文件的方式有两种:
- 先建立流对象,然后调用open()函数连接外部文件。格式如下:
流类名 对象名;
对象名.open(文件名, 模式);
- 调用流类的有参构造函数,在建立流对象的同时连接外部文件。格式如下:
流类名 对象名(文件名, 模式);
文件操作完毕后,需要关闭
关闭文件使用fstream中的成员函数close()
8.3、文件读写操作
文本文件存储数据时,以ASCII码形式保存数据
二进制文件以基本类型数据的二进制形式存放
调用ifstream或fstream的read()成员函数,从文件中读取数据
调用ofstream或fstream的write()成员函数,向文件中写入数据
打开二进制文件,需要使用binary方式,如open("person.txt", ios::out | ios::binary)
类ifstream和类fstream的成员函数get(),可以从文件中一次读取一个字节
类ofstream和类fstream的成员函数pu(),可以向文件中一次写入一个字节
8.4、随机访问文件
如果一个文件只能进行顺序存取操作,则称为顺序文件
典型的顺序文件(设备)是键盘、显示器和保存在磁带上的文件
如果一个文件可以在文件的任意位置进行存取操作,则称为随机文件
典型的随机文件时磁盘文件
顺序访问,是指严格按照数据保存的次序,从头到尾访问文件
随机访问,是指根据需要在文件的不同位置进行访问
在C++中,每个流都有一个流指针,它由系统控制,会随着对流的各种操作在字节流中移动
类istream、类ifstream和类fstream中的成员函数seekg(),可以设置文件读取指针的位置
类ostream、类ofstream和类fstream中的成员函数seekp(),可以设置文件写入指针的位置
九、函数模板与类模板
9.1、函数模板
为了提高效率,实现代码复用,C++提供了一种处理机制,函数模板
定义函数模板的一般格式如下:
1 |
|
定义函数模板,以关键字template开头,其后是使用尖括号<>
括起来的“模板参数列表”
函数指针只能指向模板的实例,不能指向模板本身
函数模板可以重载,只要它们的形参表不同即可
函数模板实例化过程由编译器完成,生成模板函数
9.2、类模板
当需要编写多个形式和功能都相似的类时,可以使用类模板机制
类是对一组对象的公共性质的抽象
类模板是对不同类公共性质的抽象
类模板属于更高层次的抽象
通过类模板,可以实例化一个个的类
声明类模板的一般格式如下:
1 |
|
如果在类模板外定义其成员函数,则要采用以下格式:
1 |
|
当使用类模板创建对象时,要随类模板名给出具体的类型形参或普通形参的具体实参,格式如下:
1 |
|
或
1 |
|
由类模板生成类的过程称为类模板的实例化
由类模板实例化得到的类称为模板类
类模板和类模板之间、类模板和普通类之间可以互相继承,它们之间的常见派生关系有以下4种情况:
- 普通类继承模板类
- 类模板继承普通类
- 类模板继承类模板
- 类模板继承模板类
如果类模板的成员函数定义在类中,则其自动成为内联函数