type
status
date
slug
summary
tags
category
icon
password
零、碎
1.选择 判断 知识点
1.逻辑运算符两侧运算对象的数据类型可以是任何类型的数据
2.
if (!x)
等价于 if (x==0||x=='0');
3.
x = ++y
++x =y
是正确的 (x+y)++
是错误的4.条件编译允许在编译时包含不同的代码
5.C++中,cin是预定义的对象
6.使用提取符(<<)可以输出各种基本数据类型的变量的值,也可以输出指针值。
7.和指针类似,引用被初始化后,还可以引用别的变量。❌
8.以下程序中,new语句干了什么。
分配了长度为20的整数指针数组空间,并将num[0]的指针返回。
9.c++中不允许嵌套函数(在一个函数中定义新函数)
10.抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出;
11.类的非静态成员函数才有this指针
12.因为静态成员函数不能是虚函数,所以它们不能实现多态。
13.
int i; int &ri=i;
对于这条语句,ri和i这两个变量代表的是同一个存储空间。 (引用)14.构造函数可以被重载,析构函数不可以被重载。因为构造函数可以有多个且可以带参数,而析构函数只能有一个,且不能带参数。
15.如果基类声明了带有形参表的构造函数,则派生类就应当声明构造函数
16.基类中的私有成员不论通过何种派生方式,到了派生类中均变成不可直接访问成员。
17.纯虚函数与函数体为空的虚函数等价。❌
18.不可以对数组类型进行整体赋值
19.函数可以返回一个不带索引的数组名
20.(B)

21.(B)

22.指向函数的指针变量 p 可以先后指向不同的同种类型的函数,但不可作加减运算。
23.数组名就是数组的起始地址,数组的指针就是数组的起始地址。
24.用指针变量作函数参数,可以得到多个变化了的值。
25.成员函数是公有的,在内存中存在一份,各个对象都可以使用
2.读程序 计算
(1)
48
(2)负数对正数取余结果为负数,正数对负数取余结果为正数
(3)运算符优先级
51
14346030运算符优先级:括号运算>加法>赋值>逗号
(4)条件运算符
1条件运算符的结合性是从右到左结合的,先算c<d?c:d返回c=3;再算a<b?a:c返回值为1。
(5)注意细节!
1个数字,for循环没有方法体输出:2
(6)
bool
(7)string
输入:ABCDE输出:ABC 3 5
(8)5 4 6
指针阅读程序
派生类构造函数
1.调用基类构造函数,对基类数据成员初始化;
2.调用子对象构造函数,对子对象数据成员初始化;
3.再执行派生类构造函数本身,对派生类数据成员初始化。
- C++声明+赋值一个对象指针时,调用默认构造函数。如果只是声明,那 么不会调用构造函数,分配空间给p,但是不可用。
- 默认构造函数(即无参构造函数)不一定存在,但是拷贝构造函数总是会存在。
一、概述
(一)程序设计语言
1.低级语言
- 机器语言(采用指令编码和数据的存储地址来表示指令的操作以及操作数)
可以直接在计算机上执行
- 汇编语言(用符号来表示指令的操作以及操作数)
必须翻译成机器语言才能执行(翻译工作由程序assembler 汇编程序 来自动完成)
2.高级语言
(二)c++程序的构成
1.每个c++程序必须有且仅有一个名字为main的全局函数,称为主函数,程序从全局函数main开始执行,main函数的返回值类型为
int
一般情况下,返回0表示程序正常结束,返回负数(如-1)表示程序非正常结束
2.一个c++程序可以存放在一个或多个文件(称为源文件)中,每个源文件包含一些程序实体的定义,其中有且仅有一个源文件中包含全局函数
main
(三)词法
1.标识符
- 标识符命名规则:
- 由大小写中英文字母、数字、下划线、美元符号($)构成
- 第一个字符不能是数字
- 标识符通常用作程序实体的名字
- 大小写字母有区别
(C++ 语言保留了一些名字供语言本身使用,这些名字不能被用作标识符。)
2.关键字
3.字面常量
4.操作符
5.标点符号
- 注释不构成可执行程序的一部分
不参加编译,也不会出现在目标程序中
- 预处理和函数头后面不需要加
;
表示结束
- 续行符
\
(将一个单词分成多行来写,需在每行最后加续行符)
(四)cpp程序的运行
编写C++程序一般需经过的几个步骤依次是:编辑、编译、连接、运行程序执行的顺序:本程序文件的main函数开始,到main函数结束
- 源程序的扩展名:
.cpp
/.h
- 目标文件:
.obj
- 可执行文件:
.exe
(五)进制转换
- 十进制整数----二进制/八进制/十六进制:除以2/8/16从下往上取余
- 十进制小数----二进制/八进制/十六进制:乘以2/8/16从上往下取整

- 二进制/八进制/十六进制----十进制:

- 二进制----八进制/十六进制:

(六)原码 反码 补码
- 原码:2进制表示 (通常最高位表示正负 1负 0正)
对于n个二进制位构成的原码,能表示的整数范围为 -(2^(n-1)-1)~2^(n-1)-1
- 补码:
- 正整数:原码
- 负整数:原码各位取反后加1
对于n个二进制位构成的补码,能表示的整数范围为 -2^(n-1)~2^(n-1)-1
(A)
- 加减法
- 加:补码直接相加,舍去最高位
- 减:减数取负,与被减数相加,舍去最高位
二、基本数据类型和表达式

(一)基本数据类型

可以使用sizeof(<类型>)或sizeof(<变量>)运算其字节长度
1.整数类型
int
2/4字节 (由计算机字长决定)
short int
short
2字节
long int
long
4字节
- 无符号整数类型
unsigned int
/unsigned
unsigned short int
/unsigned short
unsigned long int
/unsigned long
无符号整数类型所占内存大小与相应整数类型相同有符号整数类型的数,分配给其内存空间中会占用一个二进制位表示它的符号但无符号整数类型其内存空间中没有表示符号的位
2.实数类型(浮点类型)默认为double
float
4个字节
double
8个字节
long double
8/10个字节
3.字符类型
char
1个字节
wchar_t
ASCII字符集:a--97 A--65
4.逻辑类型 bool
- 1 真 true
- 0 假 false
5.空值类型
void
void*
通用指针类型
(二)表现形式
1.常量
- 字面常量 在程序中直接写出常量值的常量
- 整数类型常量
- 十进制 第一个数字不能是0(0除外)
- 八进制 由0开头
- 十六进制 由0x或0X开头
- 实数类型常量
- 小数:可以省略小数点前后的0
5.
.5
- 科学计数法:
4.2E2
= 4.2*10^2 - 字符类型常量
'A'
\101
(八进制)\x41
(十六进制)'\n’
(换行符)‘\r’
(回车符)‘\t’
(横向制表符)‘\b’
(退格符)‘\a’
(响铃)'\f'
(换页,在打印机上,屏幕上没有页)- 字符串常量 为一维字符数组
- 符号常量 有名字的常量
const double PI=3.14;
#define PI 3.14
(
#define
定义的标识符在编译前将被替换成所定义的内容)2.变量
变量有一定的生存周期和作用范围
int a=5;
int a(5);
- 定义
- 赋值:对内存空间初始化
- 使用:获取,或者改变内存空间的数值
(三)操作符
又称作:运算符 数据称为:操作数操作符的优先级:
1.算术操作符
- 加
+
、减、乘
、除
/
、取余%
- 取负 取正
+
- 自增、自减
++
-
例题:可以使x的值增大2:
++ ++x
(++x)++
❌:
x++ ++
++x++
2.关系与逻辑操作符
(1)关系操作符
>
<
>=
<=
==
!=
(2)逻辑操作符
&&
逻辑与 ||
逻辑或 !
逻辑非例:3&&5的结果为:1
解析:3:11
5:101
11&&101 = 001 = 1
短路求值
3.位操作符
(1)逻辑位操作
~
按位取反 二进制取反&
按位与例:若有变量定义 int a = 13, b = 6; 则表达式 a & b 的值为:4
13=1101
6=0110
1101 & 0110 = 0100 = 4
1101 ^ 0110 = 1011 = 11
|
按位或^
按位异或 相同为0,不同为1(x^a)^a=x
(2)移位操作
<<
左移
把第一个操作数按二进制位依次左移由第二个操作数所指定的位数。左移时,高位舍弃,低位补0。
>>
右移- 对于无符号数或有符号的非负数,高位补0
- 对于有符号数的负数,高位与原来的最高位相同(适合于补码表示的整数)
把第一个操作数按二进制位依次右移由第二个操作数所指定的位数。右移时,低位舍弃,高位按下面规则处理:
移位操作常常用于实现特殊的乘法和除法运算。例如,在某些情况下
- 把一个整型数按二进位左移一位相当于把该整型数乘以2,
- 把一个整型数按二进位右移一位相当于把该整型数除以2,
4.赋值运算符
- cpp允许连续使用赋值运算符
5.其他操作符
(1)条件操作符
d1?d2:d3
如果d1的值为true或非零,则运算结果为d2,否则为d3
(2)逗号操作符
d1,d2,d3,...
将若干个运算连接起来从左至右依次进行各个运算,操作结果为最后一个运算的
结果。
例:
x=a+b,y=c+d,z=x+y
等价于z=a+b+c+d
(3)sizeof操作符
sizeof(类型名/变量名)
计算各种数据类型的数据所占内存空间大小
CHAR_MAXSHRT_MAXINT_MAXLONG_MAXLLONG_MAX long long
typedef <已有类型> <别名>
给已有数据类型取别名
6.操作数的类型转换
(1)隐式类型转换
char
, short
, int
, unsigned int
, long int
, unsigned long int
(将
char
, signed char
, unsigned char
, short int
, unsigned short int
)float
, double
, long double
(2)显式类型转换
<类型名>(<操作数>)
(<类型名>)<操作数>
三、控制语句
(一)选择
1.if
2.switch
(二)循环
1.while
2.do-while
注意:while后的;
3.for
(三)无条件转移
1.goto
- 不能用
goto
从一个函数外部转入该函数内部,也不能用goto
从一个函数内部转到该函数外部
- 不能掠过带有初始化的变量定义
2.break
- 立即跳出循环
3.continue
- 只能用在循环语句的循环体中
- 结束当前循环,进入下一次循环
4.return
四、函数
1.函数定义
<返回值类型> <函数名> (<形式参数表>) <函数体>
例:
int factorial(int n){ }
- return语句:若返回值类型与return的类型不同,会存在隐式类型转换,把return的类型转成<返回值类型>
- 函数的定义不可以嵌套
2.函数调用
<函数名> (<实在参数表>)
例:
factorial(5);
- 实参个数和类型与函数形参相同,若类型不同,会隐式转换,把实参类型转换成形参类型。
- 函数调用执行过程
- 计算实参的值
- 把实参分别传递给被调用函数的相应形参
- 执行函数体
- 函数体中执行
return
语句返回函数调用点,调用点获得返回值(如果有返回值)并执行调用后的操作
为形参分配临时内存空间
为局部变量分配临时内存空间当函数的语句部分执行结束后,释放进入函数时所申请的所有临时变量空间,这包括形式参数和局部变量两个部分。
- 函数参数传递
- 值传递(默认)
- 地址或引用传递
3.函数声明
- 调用的函数都要有定义,若定义在调用点之后或其他文件中,需要在调用前对被调用的函数进行声明。
<返回值类型> <函数名> (<形式参数表>)
或
extren <返回值类型> <函数名> (<形式参数表>)
- 在函数声明中,形式参数表可以只列出参数类型而不写形参名
例:
int g(int,int);
- 在函数里面也可以声明
4.局部变量与全局变量
(1)局部变量
- 在复合语句中定义的变量,只能在定义他们的复合语句中使用
- 函数的形式参数与可以看成是局部变量
(2)全局变量
- 在函数外部定义的变量
- 若全局变量定义在使用点之后或其他文件里,使用前需对其声明。
extern <变量类型> <变量名>
- 变量定义也属于一种声明:定义性声明
- 变量定义要给变量分配空间,变量声明则不用
- 变量定义要给变量赋初值(初始化变量),变量声明则不可以。
- 变量定义只能有一个,变量声明可以有多个。
static
全局变量:存储在静态存储区,在函数外部定义,只限在本文件中使用
extern
全局变量:存储在静态存储区,在其他文件中定义,在本文件中可以使用static
局部变量:存储在静态存储区,在函数内部定义,只限在函数内部使用5.程序的多模块结构
- 一个程序模块包含两个部分:
- 接口 interface (.h文件 头文件)在本模块中定义的、提供给其他模块使用的一些程序实体的定义(常量、类型等)和声明(函数、全局变量等)
- 实现 implementation (.cpp文件 源文件)模块中程序实体的定义
- 在一个模块A中用到另一个模块B中定义的全局程序文件,要在A的源文件中用一条编译预处理命令(
#include
)把B的头文件中的内容包含进来,达到声明的目的。
- 文件包含命令:
#include <文件名>
或#include "文件名"
- <文件名> 表示在系统指定的目录下寻找指定文件
- "文件名" 表示先在#include命令的源文件所在的目录下查找,然后再在系统指定的目录下寻找指定文件
include命令的含义是:在编译前,用文件名所指定的文件内容替换该命令
如果几个目录中都有xx.h,#include "xx.h"最多只会搜索到第一个就停止继续搜索
6.标识符的作用域
(1)局部作用域
- 在函数定义或复合语句中、从标识符的定义点开始到函数定义或复合语句结束之间的程序段。
- 具有局部作用域的标识符:局部常量名、局部变量名/对象名、函数的形参名
- 如果在一个标识符的局部作用域中包含内层复合语句,并且在该内层复合语句中定义了一个同名的不同实体,则外层定义的标识符的作用域应该是从其潜在作用域中扣除内层同名标识符的作用域之后所得到的作用域。
(2)全局作用域
(具有全局作用域的标识符主要用于标识被程序各个模块共享的程序实体)
- 构成c++程序的所有模块(源文件)
- 具有全局作用域的标识符:全局函数名、全局变量名/对象名、全局类名
- 若标识符的定义点在其它源文件中或在本源文件中使用点之后,则在使用前需要声明它们。
- 如果在某个局部作用域中定义了与某个全局标识符同名的标识符,则该全局标识符的作用域应扣掉与之同名的局部标识符的作用域。
若在局部标识符的作用域中要使用与其同名的全局标识符,需要用全局域选择符(
::
)对全局标识符进行修饰(受限)(3)文件作用域
(具有文件作用域的标识符用于标识在一个模块内部共享的程序实体)
- 在全局标识符的定义中加上
static
const
定义的全局常量名
- 具有文件作用域的标识符只能在定义他们的源文件(模块)中使用
(4)函数作用域
- 语句标号 一个语句标号只能定义一次
(5)函数原型作用域
- 用于函数声明的函数原型,其中的形式参数名的作用域从函数原型开始到函数原型结束。
void f(int x, double y);
其中的x和y的作用域是从(
开始到)
结束
(6)名空间作用域
- 在一个源文件中要用到两个分别在另外两个源文件中定义的不同全局程序实体(如:全局函数),而这两个全局程序实体的名字相同,C++提供了名空间(namespace)设施来解决上述的名冲突问题。
- 在一个名空间中定义的全局标识符,其作用域为该名空间。
- 当在一个名空间外部需要使用该名空间中定义的全局标识符时,可用该名空间的名字来修饰或受限。
7.变量的生存期(存储分配)
- 生存期:程序运行时一个变量占有内存空间的时间段
- 静态生存期
从程序开始执行时就进行内存空间分配,直到程序结束才收回它们的空间。全局变量具有静态生存期。静态数据区,系统将未显式初始化的变量初始化为0
- 自动生存期
- 局部变量默认存储类为
auto
使其具用自动生存期 register
使局部变量具有自动生存期,由编译程序根据CPU寄存器的使用情况来决定是否存放在寄存器中static
使局部变量具有静态生存期 只在函数第一次调用时进行初始化,以后调用中不再进行初始化,它的值为上一次函数调用结束时的值。
内存空间在程序执行到定义它们的复合语句(包括函数体)时才分配,当定义它们的复合语句执行结束时,它们的空间将被收回。局部变量和函数的参数一般具有自动生存期。栈区 M
在全局标识符的定义中,static用于把全局标识符的作用域改为文件作用域在局部变量的定义中,static
用于指出相应的局部变量具有静态生存期。
- 动态生存期
内存空间在程序中显式地用
new
操作或malloc
库函数分配、用delete
操作或free
库函数收回。动态变量具动态生存期。在 堆区 (大小:G)中分配8.宏定义
一种编译预处理命令
#define <宏名> (<参数表>) <文字串>
例:
#define max(a,b) ((a)>(b)?(a):(b))
9.内联函数
- 在定义函数定义时,在函数返回类型之前加上一个
inline
- 编译时,直接将被调函数体的代码直接插到调用处
- 可以提高程序的运行速度
- 有些函数即使加上了
inline
关键词,编译程序也不会把它作为内联函数来对待(是否内联由编译器决定)
10.带默认值的形式参数
- 有默认值的形参应处于形参表的右部。例如:
void f(int a, int b=1, int c=O);//OK
void f(int a, int b=1, int c); //Error
- 对参数默认值的指定只在函数声明处有意义。
- 在不同的源文件中,对同一个函数的声明可以对它的同一个参数指定不同的默认值;在同一个源文件中,对同一个函数的声明只能对它的每一个参数指定一次默认值。
11.函数名重载
- 相同函数名,具有不同的参数列表(参数的类型或个数不同)
参数类型和个数相同,只有返回值类型不同不能对他们重载函数名
- 确定一个对重载函数的调用对应着哪一个重载函数定义的过程称为绑定(binding,又称定联、联编、捆绑)。
- 按参数类型匹配优先级:
- 精确匹配:细微的转换(数组名转化成第一个元素的指针、函数名转换成函数指针等)后相同
- 提升匹配
- 标准转换匹配
- 任何算术类型可以互相转换
- 枚举类型可以转换成任何算术类型
- 零可以转换成任何算术类型或指针类型
- 任何类型的指针可以转换成void *
- 派生类指针可以转换成基类指针
- 每个标准转换都是平等的
- 自定义转换匹配
12.λ(lambda)表达式
- 匿名函数
[<环境变量使用说明>] <形式参数> <返回值类型> <函数体>
- 空:不能使用外层作用域中的自动变量
- &:按引用方式使用外层作用域中的自动变量(可以改变这些变量的值)
- =:按值方式使用外层作用域中的自动变量(不可以改变这些变量的值)
<环境变量使用说明>:
可以使用& = 统一指定外层作用域中的自动变量的使用方式,与可以在变量名前加$ =(默认为=)单独指定
五、构造数据类型
(一)枚举
设计者自己来定义值集的数据类型
1.枚举类型的定义
enum <枚举类型名> {<枚举值表>}
例:
enum Day {SUN,MON,TUE,WED,THU,FRI,SAT}
默认情况下,第一个枚举值为0,以此加1,也可以显式地给枚举值指定值。
例:
enum Day {SUN=7,MON=1,TUE,WED,THU,FRI,SAT}
TUE=2,WED=3......- 枚举类型变量的定义:
<枚举类型名><变量表>;
或enum <枚举类型名><变量表>;
- 枚举类型和枚举类型变量同时定义:
enum Day {SUN,MON,TUE,WED,THU,FRI,SAT}d1,d2,d3;
2.枚举类型的运算
- 赋值:
- 一个枚举类型的变量只能在相应枚举类型的值集中取值。
- 相同枚举类型之间可以进行赋值操作。
- 可以把一个枚举值赋值给一个整型变量。
- 但不能把一个整型值赋值给枚举类型的变量.
day = 1;
int a = d1;
d1 = a;
d1 = (Day)a;//可以,但不安全
- 比较:
系统首先将枚举值转换为对应的整型值,然后进行比较。
- 算术运算:
运算时,将枚举值转换为对应的整型值。
- 不能对枚举类型的值直接进行输入,但可以进行输出。
例:cin >> d
(二)数组
1.一维数组
(1)定义:
- 直接定义变量
int a[10]
- 定义数组类型,再定义变量
typedef int A[10];
A a;
(数组类型的元素个数是固定的,在程序执行中不能改变)
- 不能通过赋值修改数组长度
(2)变量的初始化
int a[10]={1,2,3,4,5,6,7,8,9,10};
若初始化表中的值的个数少于数组元素个数,则不足部分的数组元素初始化为0
int c[]={1,2,3};
若对每个元素都进行了初始化,可以省略元素个数,元素个数由初始化的个数来定
- 若不使用
={}
赋初值时(此时一定会定义长度),static
和全局数组均默认其为0或‘0’,其他局部数组赋值随机
2.一维字符数组
- 在字符串中最后一个字符的后面存储一个
'\0'
,作为字符串的结束标记
- 若初始化表中的值的个数少于数组元素个数,则不足部分的数组元素初始化为'\0'
- 初始化:
char s[10]={'h','e','l','l','o','\0'};
(只有这种形式程序中必须显式的加上
'\0'
)3.二维数组
(1)定义:
int a[10][5]
typedef int A[10][5];
A a;
(2)初始化:
int a[2][3]={{1,2,3},{4,5,6}};
或int a[2][3]={1,2,3,4,5,6};
初始化的值可以少于数组元素的个数,元素默认初始化为0
- 数组的行数可以省略,其行数由初始化的个数来决定(只能省略最高维)
int a[][3]={{1,2,3},{4,5,6},{7,8,9}};
由于不存在数组的长度这个属性,在将数组作为函数参数时,通常同时将长度作为参数传输在main函数中可以使用sizeof(a)
获得数组长度
(三)结构类型
1.定义:
- 结构类型定义
struct <结构类型名> {<成员表>};
例:
- 变量定义
<结构类型名> <变量名表>
或struct <结构类型名> <变量名表>
例:
Student a,b,c;
- 也可以在定义结构类型的同时定义结构类型的变量,这时结构类型名可以省略,例:
struct默认访问权限是public,class默认访问权限是private
2.初始化:
定义结构类型时不能对其成员初始化。因为类型不是程序运行时刻的实体,他们不占有内存空间,初始化没意义。可以在定义变量时初始化,例:
Student a={2,Amy,FEMALE};
3.访问结构的成员
<结构类型变量>.<成员名>
- 不同结构类型的成员的名字可以相同,它们可以与程序中非结构成员的名字相同;
- 结构类型的名字可以与同一作用域中的其他非结构类型标识符相同;
对于上述这种情况,使用结构类型A必须要在结构类型名前加上关键字
struct
4.结构数据的赋值
- 对结构类型的数据可以整体赋值,但此操作必须要在相同的结构类型之间进行,不同类型结构之间不能相互赋值。
(四)联合类型
1.定义:
- 联合类型定义 例:
联合类型的所有成员占有同一块内存空间,该内存空间的大小为其最大成员所需要的内存空间的大小。
- 可以进行整体赋值,可传给函数,可作为函数返回值
(五)指针类型
1.指针类型的定义
指针是内存地址的抽象表示,一个指针代表了一个内存地址获取变量的地址:&<变量名>
每一个地址都属于某一种指针类型
<类型> *<指针变量>
例:
int *p,*q; //p q均为指针变量
int *p,q; //p为指针变量,q为int型变量
int* p,q; //p为指针变量,q为int型变量
typedef <类型>* <指向数据类型数据的指针类型>;<指向数据类型数据的指针类型> <指针类型的变量名>;
例:
typedef int* Pointer;
Pointer p,q;
void *p
表明该指针变量可以指向任意类型的数据
- 符号常量
NULL
空指针
指针变量拥有自己的内存空间,在该空间中存储的是另一个数据的内存地址例:int x=1; int *p=&x;
2.指针类型的基本操作
(1)赋值
任意类型的都可以赋给void*类型的指针变量
(2)间接访问操作
<指针变量>
访问指针变量所指向的变量
使用指针变量前,必须先给它赋一个指向合法具体对象的地址值Error:int *px; *px = x;
Error:char *s; cin>>s;
(3)指针的运算
①一个指针加上或减去一个整型值
通常用此访问数组元素一个指针可以与一个整型值进行加或减运算,运算结果为与该指针同类型的指针

y = *px++;
相当于 y = *px (px++)
(取当前元素,指向下一个)②两个同类型的指针相减
两个同类型的指针相减,结果为两个指针之间相差元素的个数两个指针不能相加
③两个同类型的指针比较
即:比较他们所对应的内存地址的大小
(4)指针的输出
- 当输出字符指针
char
时,输出的不是指针值,而是该指针所指向的字符串(特例)
3.指针作为参数类型
(1)提高传参效率
(2)通过参数返回函数的计算结果
(3)指向常量的指针
- 指向常量的指针(常量指针)
const <类型> *<指针变量>
- 指针类型的常量(指针常量)是一个常量,但是是指针修饰的
const <类型> * 常量指针:不可以改变值,可以改变指向* const 指针常量:不可以改变指向,可以改变值
- 指向常量的指针常量
例:C(A B相同)
(4)作为返回值类型
- 不能把局部量的地址作为指针返回给调用者
4.指针与动态变量
- 数组元素个数不能是变量,必须在编译时就能确定它的值是多少
int n; cin >> n; int a[n];
(1)动态变量的创建
动态变量是指在程序运行中,由程序根据需要所创建的变量。
①new <类型名>
new
操作类型应保持一致②new <类型名> [][]
除第一维的大小外,其他维的大小必须是常量或常量表达式
- 如何创建一个m行、n列的动态数组?
- 用一维数组实现:
int *p=new int[m*n];
- 第i行、第j列元素:
(p+i*n+j)
③void *malloc(unsigned int size)
#include <cstdlib>
new
malloc
区别:new
自动计算所需分配的空间大小,而malloc
需要显式指出new
自动返回相应类型的指针,而malloc
要做显式类型转换
(2)动态变量的访问
动态变量没有名字,对动态变量的访问需要通过指向动态变量的指针变量来进行(间接访问)。
(3)动态变量的撤销
- 在C++中,动态变量需要由程序显式地撤消(使之消亡)
例如:
delete p;//撤消p指向的int型动态变量
或free(p);
再例如:
delete []q;//撤消q指向的动态数组
或 free(q);
- 一般来说,用new创建的动态变量需要用delete来撤销;用malloc创建的动态变量则需要用free撤销。
①delete <指针变量>
②delete []<指针变量>
③void free(void *p)
- 用delete和free只能撤消动态变量!
int x,*p; p = &x; delete p;
- 用delete和free撤消动态数组时,其中的指针变量必须指向数组的第一个元素
int *p = new int[n]; p++; delete []p;
- 悬浮指针
用delete或free撤消动态变量后,C++编译程序一般不会把指向它的指针变量的值赋为0,这时该指针指向一个无效空间。
- 内存泄漏
没有撤消动态变量,而把指向它的指针变量指向了别处或指向它的指针变量的生存期结束了,这时,这个动态变量存在但不可访问(这个动态变量已成为一个“孤儿”),从而浪费空间。
5.指针与数组
指针访问数组元素能提高效率
(1)一维数组的首地址
①通过数组首元素来获得
- 把一维数组传给一个函数时,编译器也会对数组变量进行类型转换 例:
int a[10]; f(a);
相当于f(&a[0]);
- 字符串常量也可隐式转换成他的第一个字符在内存中的首地址。
②通过整个数组获得
- 当创建一个动态的一维数组时,得到的是第一个元素的地址。例如:
(2)多维数组的首地址
int b[2][3]={1,2,3,4,5,6}
①通过第一行第一列元素来获得
- 访问元素
p[i]
(p+i)
②通过第一行的一维数组来获得
- 访问元素
q[i][j]
(*(q+i)+j)
q++
(此时指向第1行) **q
4(第1行第0个元素) *(*q+1)
5(第1行第1个元素) (*q)[1]
5③通过整个数组来获得
- 访问元素
r[0][i][j]
(r)[i][j]
- 例题:
- 对于一个动态的n维数组,实际上是按一维动态数组来创建的,返回的首地址类型是去掉第一维后的数组指针类型。例如,下面创建一个动态的二维数组:
(3)函数main的参数
- 可以给函数main定义参数,其定义格式为:
int main(int argc, char *argv[]);argc
表示传给函数main的参数的个数,argv
表示各个参数,它是一个一维数组,其每个元素为一个指向字符串的指针。
- 以
“copy file1 file2”
执行程序copy时,copy的函数main将得到参数: argc:3 argv[0]: "copy"
argv[1]: "file1"
argv[2]: "file2"
6.函数指针
- C++中可以定义一个指针变量,使其指向一个函数。
- 函数指针定义格式:
<返回类型> (*<指针类型>)(<形式参数表>)
- 对于一个函数,可以用取地址操作符
&
来获得它的内存地址,或直接用函数名来表示。
- 通过函数指针调用函数可采用下面的形式

A:指向整型量的指针
B:指向字符型的指针
C:由指向字符的指针构成的数组,即指针数组
D:指向字符数组的指针,即数组指针
F:返回值为指向整型量的指针的函数,即指针函数
G:指向返回值为整型量的函数的指针,即函数指针
7.多级指针
(可能不考 待补充)
(六)引用类型(变量的别名)
1.定义
- 定义格式:
<类型> &<引用变量>=<变量>
- 引用类型用于给一个变量取一个别名。
- 在语法上, 对引用类型变量的访问与非引用类型相同。
- 在语义上, 对引用类型变量的访问实际访问的是另一个变量(被引用的变量)。 效果与通过指针间接访问另一个变量相同。
- 对引用类型需要注意下面几点:
- 定义引用类型变量时,应在变量名加上符号“&”,以区别于普通变量。
- 定义引用变量时必须要有初始化,并且引用变量和被引用变量应具有相同的类型。
- 引用类型的变量定义之后,它不能再引用其它变量。
引用本质:指针常量指针指向不可变,指针指向的值可变
2.引用类型作为函数的参数类型
- 通过形参改变实参的值
- 指针的引用
- 引用做函数返回值类型
- 通过把形参定义成对常量的引用,可以防止在函数中通过引用类型的形参改变实参的值。
- 引用类型与指针类型的区别
- 引用类型和指针类型都可以实现通过一个变量访问另一个变量,但在语法上,
- 引用是采用直接访问形式
- 指针则需要采用间接访问形式
- 在作为函数参数类型时,
- 引用类型参数的实参是一个变量的名字
- 指针类型参数的实参是一个变量的地址
- 在定义时初始化以后,
- 引用类型变量不能再引用其它变量
- 指针类型变量可以指向其它的变量
- 引用类型一般作为指针类型来实现(有时又把引用类型称作隐蔽的指针,hidden pointer)
- 能够用引用实现的指针功能,尽量用引用!
六、对象与类
(一)面向对象程序设计
1.基础
(1)数据抽象
- 数据的使用者只需要知道对数据所能实施的操作以及这些操作之间的关系,而不必知道数据的具体表示。
(2)数据封装
- 指把数据及其操作作为一个整体来进行描述。
- 数据的具体表示对使用者是不可见的,对数据的访问只能通过封装体所提供的对外接口(操作)来完成。
(3)栈
- 栈是一种由若干个具有线性次序关系的元素所构成的复合数据。对栈只能实施两种操作:
- 进栈(push):往栈中增加一个元素
- 退栈(pop):从栈中删除一个元素
上述两个操作必须在栈的同一端(称为栈顶,top)进行。后进先出
(Last In First Out,简称LIFO)是栈的一个重要性质
2.对象和类
- 对象是由数据及能对其实施的操作所构成的封装体,它属于值的范畴。
- 类描述了对象的特征(包含哪些数据和操作),它属于类型的范畴(对象的类型)。
- 数据:数据成员、成员变量、实例变量、对象的局部变量等
- 操作:成员函数、方法、消息处理过程等
3.继承(Inheritance)
- 在定义一个新类(派生类、子类)时,可以利用已有类(基类、父类)的一些特征描述。
- 单继承与多继承
- 作用:分类、代码复用等
4.多态与绑定
多态性(Polymorphism) 动态绑定(Dynamic Binding)·
(1)多态
- 某一论域中的一个元素存在多种解释。通常体现为:
- 一名多用:
- 函数名重载
- 操作符重载
- 类属性:
- 类属函数:一个函数能对多种类型的数据进行操作。
- 类属类型:一个类型可以描述多种类型的数据。
- 面向对象程序特有的多态(动态多态):
- 对象类型的多态:子类对象既属于子类,也属于父类
- 对象标识的多态:父类的引用或指针可以引用或指向子类对象
- 消息的多态:一个消息集有多种解释(父类与子类有不同解释)
(2)绑定
- 确定对多态元素的某个使用是多态元素的那一种形式。
- 静态绑定(Static Binding,也称前期绑定,EarlyBinding):在编译时刻确定。
- 动态绑定(Dynamic Binding,也称后期绑定或延迟绑定Late Binding):在运行时刻确定。
- 多态带来的好处:
- 易于实现程序高层(上层)代码的复用。
- 使得程序扩充变得容易(只要增加底层的具体实现)。
- 增强语言的可扩充性(操作符重载等)。
(二)类
对象构成了面向对象程序的基本计算单位,而对象的特征则由相应的类来描述。类也可看成是对象的集合。
1.数据成员
- 数据成员指类的对象所包含的数据,它们可以是常量和变量。数据成员的说明格式与非成员数据的声明格式相同
- 说明数据成员时不允许进行初始化。
2.成员函数
- 成员函数描述了对类定义中的数据成员所能实施的操作。
- 成员函数的实现也可以放在类定义外
- 类成员函数名是可以重载的(析构函数除外),遵循一般函数名的重载规则
- 成员函数可以对其形参设置默认值
3.成员的访问控制
- 在C++的类定义中,默认访问控制是private(结构和联合成员的默认访问控制为public) 可以有多个public、private和protected访问控制说明
(三)对象
1.对象的创建
(1)直接方式
Date today,yesterday
- 全局对象:在函数外定义的对象
- 局部对象:在函数内定义的对象
(2)间接方式(动态对象)
- 在程序运行时刻,通过
new
操作来创建对象,用delete
操作来撤消(使之消亡)。
new创建对象自动调用构造函数 delete释放对象自动调用析构函数而malloc
与free
则否
- 通过指针来标识和访问。
- 单个动态对象的创建与撤消
- 动态对象数组的创建与撤消
2.对象的操作
- 非动态对象
<对象>.<类成员>
- 动态对象
<对象指针>-><类成员>
或(*<对象指针>).<类成员>
- 在类的外部,通过对象来访问类的成员时要受到类成员访问控制的限制
- 可以对同类对象进行赋值
- 取对象地址
- 把对象作为实参传给函数以及作为函数的返回值等操作。
3.this
指针
- 类定义中说明的数据成员(静态数据成员除外)对该类的每个对象都有一个拷贝。
- 实际上,每一个成员函数都有一个隐藏的形参
this
,其类型为:<类名>*const this;
- 成员函数所属对象的指针,明确地表示了成员函数当前操作数据所属对象
- 在成员函数中对类成员的访问是通过this来进行的。
- 一般情况下,类的成员函数中不必显式使用this指针来访问对象的成员(编译程序会自动加上)。
- 如果成员函数中要把this所指向的对象作为整体来操作(如:取对象的地址),则需要显式地使用this指针。
(四)对象的初始化和消亡前处理
1.构造函数(Constructors)
(1)定义
- 构造函数是类的特殊成员函数,它的名字与类名相同、无返回值类型。创建对象时,构造函数会自动被调用。
- 构造函数可以重载,其中,不带参数的(或所有参数都有默认值的)构造函数被称为默认构造函数。(可以不用实参进行调用的构造函数)
- 构造函数一般为
public
可以设置为private
(2)调用
- 对象创建后不能再调用构造函数,构造函数的调用是对象创建过程的一部分。
(3)成员初始化表
- 对于常量数据成员和引用数据成员(某些静态成员除外),不能在说明它们时初始化,也不能采用赋值操作对它们初始化(说明数据成员时不允许初始化)
- 对于常量数据成员和引用数据成员,可以在定义构造函数时,在函数头和函数体之间加入一个成员初始化表来对它们进行初始化。例如:
- 成员初始化表中成员初始化的书写次序并不决定它们的初始化次序,数据成员的初始化次序由它们在类定义中的说明次序来决定。
- 先初始化再执行构造函数体
例题:假定MyClass为一个类,执行MyClass a[3],*p[2];语句时会自动调用该类构造函数 ( 3 ) 次
2.析构函数(Destructors)
~<类名>
没有返回类型、不带参数、不能被重载- 一个对象消亡时,系统在收回它的内存空间之前,将会自动调用析构函数。
- 可以在析构函数中完成对象被删除前的一些清理工作(如归还对象额外申请的资源等)。
- 析构函数的调用顺序与构造函数的调用顺序完全相反

注意:系统为对象s1分配的内存空间只包含len和str(指针)本身所需的空间,str所指向的空间不由系统分配,而是由对象作为资源自己处理!
- 析构函数可以显式调用
- 把string类的对象s变成空字符串的对象,显式调
s1.~String();
(只归还对象的资源,对象并未消亡!)
- 注意:一般不需要自定义析构函数
- 需要时系统隐式提供,如:需要调用成员对象类和基类的析构函数
- 归还资源时需自定义
总结对比
构造函数的特殊性质
- 构造函数的名字必须与类名相同
- 构造函数不指定返回类型,它隐含有返回值,由系统内部使用
- 构造函数可以有一个或多个参数,因此构造函数可以重载
- 在创建对象时,系统会自动调用构造函数
析构函数的特殊性质
- 析构函数名是在类名前加
~
符号
- 析构函数不指定返回类型,它不能有返回值
- 析构函数没有参数,因此析构函数不能重载,一个类中只能定义一个析构函数
- 在撤销对象时,系统会自动调用析构函数
- 析构与构造顺序相反
- 如果一个类没有定义构造和析构函数,则编译器将生成默认构造函数(不必为其提供参数的构造函数)和默认析构函数
例题1
对象数组
- 数组元素为对象的数组,即数组中每个元素都是同一个类的对象
- 对象数组的格式:
<类名><数组名>[<大小>]…..
DATE dates[5];
使用对象数组成员:<数组名>[<下标>].<成员名>
dates[0].year
自由存储对象
- 在程序运行过程中根据需要可以随时建立或者删除的对象称为自由存储对象(建立及删除可使用new和delete)
3.成员对象的初始化
- 成员对象:
对于类的数据成员,其类型可以是另一个类。也就是说,一个对象可以包含另一个对象(称为成员对象)
- 成员对象由成员对象类的构造函数初始化:
- 如果在包含成员对象的类中,没有指出用成员对象类的什么构造函数对成员对象初始化,则调用成员对象类的默认构造函数。
- 可以在类构造函数的成员初始化表中显式指出用成员对象类的某个构造函数对成员对象初始化。
- 创建包含成员对象的类的对象时,先执行成员对象类的构造函数,再执行本身类的构造函数。
- 初始化成员对象时,若调用成员对象的非默认构造数,必用成员初始化列表
- 若成员初始化表为空,则调用成员对象的默认构造函数完成初始化
- 一个类若包含多个成员对象,这些对象的初始化次序按它们在类中的说明次序(而不是成员初始化表的次序)进行。
- 析构函数的执行次序与构造函数的执行次序正好相反。
- 先析构类对象,再析构成员对象
- 若有多个成员对象,则析构与构造次序相反
4.拷贝构造函数
- 在创建一个对象时,若用一个同类型的对象对其初始化,这时将会调用一个特殊的构造函数:拷贝构造函数。
- 在三种情况下,会调用类的拷贝构造函数:
- 定义对象时,例如:
- 把对象作为值参数传给函数时,例如:
- 把对象作为函数的返回值时,例如:
- 如果程序中没有为类提供拷贝构造函数,则编译器将会为其生成一个隐式拷贝构造函数。
- 隐式拷贝构造函数将逐个成员拷贝初始化:
- 对于普通成员:它采用通常的初始化操作;
- 对于成员对象:则调用成员对象类的拷贝构造函数来实现成员对象的初始化 。
- 一般情况下,编译程序提供的默认拷贝构造函数的行为足以满足要求,类中不需要自定义拷贝构造函数。
- 在有些情况下必须要自定义拷贝构造函数,否则,将会产生设计者未意识到的严重的程序错误:
- 浅拷贝
- 系统提供的隐式拷贝构造函数将会使得a1和a2的成员指针p指向同一块内存区域!如果对一个对象操作之后修改了这块空间的内容,则另一个对象将会受到影响。如果不是设计者特意所为,这将是一个隐藏的错误。当对象a1和a2消亡时,将会分别去调用它们的析构函数,
这会使得同一块内存区域将被归还两次,从而导致程
序运行异常。深拷贝解决上面问题的办法是在类A中显式定义一个拷贝构造函数。
A::A(const A& a){ x = a.x; y = a.y; p = new char[strlen(a.p)+1]; strcpy(p,a.p);}
- 隐式拷贝构造函数会调用成员对象的拷贝构造函数
- 自定义的拷贝构造函数将默认调用成员对象类的默认构造函数对成员对象初始化!


(五) 进阶
1.常(const)成员函数
(1)常成员函数
- 为了防止在获取对象状态的成员函数中改变对象的状态,可以把它们说明成
const
成员函数。
- 声明与定义时都应加上
const
const
成员函数不能改变对象的状态(数据成员的值)。
- 给成员函数加上const修饰符还有一个作用:描述对常量对象所能进行的操作(常对象只能调用常成员函数)。
- 常成员函数不能调用非常成员函数。
const成员函数可以修改static成员变量 ✓ const修饰this指针指向内容不可更改 ✓ static变量不用this指针访问const成员函数不能修改对象的任何数据成员
(2)常对象
- 使用const关键字修饰的对象称为常对象
<类名> const <对象名>
或者const <类名> <对象名>
- 常数据成员:
const
说明的数据成员,只能通过构造函数的成员初始化列表显式进行初始化
- 注意:
- 常对象在定义时必须进行初始化,而且不能被更新
- 常对象只能调用它的常成员函数
- 一般对象既可以调用常成员函数,也可以调用一般成员函数
- 对于成员函数,
const
参与函数重载的区分 - 常成员函数可以直接访问类的常数据成员及一般数据成员
常对象调用常成员函数,一般对象调用一般成员函数
2.静态数据成员
(1)静态数据成员
- 可通过静态数据成员来实现属于同一个类的不同对象之间的数据共享
- 类的静态数据成员对该类的所有对象只有一个拷贝。
- 往往在类的外部给出定义并进行初始化。在函数内部声明。
- 需要通过对象来访问。
a.x
(2)静态成员函数
- 静态成员函数可以通过对象来访问外,也可以直接通过类来访问。
A::get_shared();
或A a; a.get_shared();
- 静态成员函数可以直接访问类的静态成员
- 静态成员函数不能直接访问类的非静态成员
静态成员函数没有隐藏的this参数
- 若要访问非静态成员,必须通过参数传递的方式得到相应的对象,再通过对象进行访问
3.友元(friend)
- 指定与一个类密切相关的、又不适合作为该类成员的程序实体(某些全局函数、某些其它类或某些其它类的某些成员函数)可以直接访问该类的private和protected成员。这些程序实体称为该类的友元。
- 友元关系具有不对称性,不具有传递性,友元关系不能被继承。
- 友元是数据保护和数据访问效率之间的一种折中方案。(破坏了类的封装性)
- 一个类的友元函数不属于这个类。
4.转移构造函数
5.操作符重载
(1)概述
- 操作符重载实质上是函数重载
- 可以重载C++中除下列操作符外的所有操作符:
成员选择符
.
,间接成员选择符.*
>*
,条件操作符?:
,域解析符::
,sizeof
- 重载操作符时,其操作数中至少应该有一个是类、结构、枚举以及它们的引用类型。
- 操作符重载可通过下面两个途径来实现:
- 作为一个类的非静态的成员函数(
new
和delete
除外)。 - 作为一个全局(友元)函数。
- 一般情况下,操作符既可以作为全局函数,也可以作为成员函数来重载。
- 在有些情况下,操作符只能作为全局函数或只能作为成员函数来重载。
(2)双目操作符重载
① 作为成员函数重载
- 重载函数的声明格式
- 重载函数的定义格式
- 使用格式
例:
② 作为全局(友元)函数重载
- 定义格式
- 使用格式:
例:
表达式x=operator-(y,z)还可以表示为 x=y-z
(3)单目操作符重载
①作为成员函数重载
- 定义格式
- 使用格式
<类名> a; #a
或a.operator#()
②作为全局(友元)函数重载
- 定义格式
<返回值类型> operator #(<类型> <参数>) { ...... }
- 使用格式
<类型> a; #a
或operator#(a)
操作符++和-- 的重载
- 操作符++(--)有前置和后置两种用法:
class Counter{ int value; public: Counter() { value = 0; } Counter& operator ++(){ //前置的++重载函数 value++; return *this; } const Counter operator ++(int){ //后置的++重载函数 Counter temp=*this; //保存原来的对象 value++; //写成:++(*this);更好!调用前置的++重载函数 return temp; //返回原来的对象 }};Counter a,b,c;b = ++a; //使用的是上述类定义中不带参数的操作符++重载函数c = a++; //使用的是上述类定义中带int型参数的操作符++重载函数++(++a); (++a)++; //OK++(a++); (a++)++; //Error
(4)特殊操作符的重载
①赋值操作符“=”的重载
- C++编译程序会为每个类定义一个隐式的赋值操作符重载函数,其行为是:逐个成员进行赋值操作
- 参照浅拷贝,解决上面问题的办法是自己定义赋值操作符重载函数
- 自定义的赋值操作符重载函数不会自动地去进行成员对象的赋值操作,必须要在自定义的赋值操作符重载函数中显式地指出。
- 赋值操作符只能作为非静态的成员函数来重载。
若使用全局函数,则会与隐式赋值操作符重载函数存在歧义
- 一般来讲,需要自定义拷贝构造函数的类通常也需要自定义赋值操作符重载函数。
②访问数组元素操作符“[] ”的重载
- 对于由具有线性关系的元素所构成的对象,可通过重载“[]”,实现对其元素的访问。
③重载操作符new
- 操作符
new
必须作为静态的成员函数来重载(static
说明可以不写) - 返回类型必须为
void *
- 参数表示对象所需空间的大小,其类型为size_t(unsigned int)
- new重载函数可以有多个(参数需有所不同)
void *operator new(size_t size);
- 重载new时,除了对象空间大小参数以外,也可以带有其它参数
void *operator new(size_t size,XXX);
使用格式:
p = new (XXX) A(...);
④重载操作符delete
- 与重载操作符new相同
- 操作符delete也必须作为静态的成员函数来重载(
static
说明可以不写)
- delete重载函数只能有一个
⑤函数调用操作符“()”
⑥类成员访问操作符“->”的重载
- “->”为一个双目操作符,其第一个操作数为一个指向类或结构的指针,第二个操作数为第一个操作数所指向的类或结构的成员。
- 通过对“->”进行重载,可以实现一种智能指针(smart pointers):
一个具有指针功能的对象,通过该对象访问所“指向”的另一个对象时,在访问所指向对象的成员前能做一些额外的事情。
智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露(利用自动调用类的析构函数来释放内存)。
必用成员函数重载
⑦带一个参数的构造函数
- 带一个参数的构造函数可以用作从一个基本数据类型或其它类到某个类的转换。
⑧自定义类型转换
- 自定义类型转换,从一个类转换成基本数据类型或其它类(不需要返回值类型的声明)
七、继承(类的复用)----派生类
(一)概述
1.继承关系
- 在继承关系中存在两个类:基类(或称父类)和派生类(或称子类)。
- 派生类拥有基类的所有特征,并可以
- 定义新的特征
- 或对基类的一些特征进行重定义。
(二)单继承
单继承时,派生类只能有一个直接基类
1.单继承派生类的定义:
- 定义:
<继承方式>指出对从基类继承来的成员的访问控制,可以是
public
private
protected
- 派生类除了拥有新定义的成员外,还拥有基类的所有成员(除了基类的构造/析构函数和赋值操作符重载函数)
- 定义派生类时一定要见到基类的定义。
- 如果在派生类中没有显式说明,基类的友元不是派生类的友元;如果基类是另一个类的友元,而该类没有显式说明,则派生类也不是该类的友元。
2.访问基类成员
- 派生类不能直接访问基类的私有成员。
protected
用它说明的成员不能通过对象使用,但可以在派生类中使用。
- 派生类对基类成员的访问除了受到基类的访问控制的限制以外,还要受到标识符作用域的限制。
- 即使派生类中定义了与基类同名但参数不同的成员函数,基类的同名函数在派生类的作用域中也是不直接可见的,可以用基类名受限方式来使用之
- 也可以在派生类中使用using声明把基类中某个的函数名对派生类开放
3.继承方式
- 默认的继承方式为:
private
B.png?table=block&id=5aa3d4d3-19b1-4898-8ee2-90aa10d1d7d1&t=5aa3d4d3-19b1-4898-8ee2-90aa10d1d7d1&width=677.984375&cache=v2)
- 可在派生类中分别调整基类各成员的访问控制属性(基类private成员除外)
class A{ public: void f1(); protected: void g1();};class B: private A{ public: A::f1;//f1调整为public A::g1;//g1调整为public}
- 可以将派生类对象赋值给基类对象
可以将派生类对象的地址赋值给基类指针
可以将派生类对象赋值给基类的引用
- 派生类对象不能赋值给派生类对象。
派生类指针变量不能指向基类对象。
派生类操作不能用于基类对象。
4.初始化和赋值操作
①初始化
- 派生类构造函数必须负责调用基类构造函数,并对其所需要的参数进行设置
- 派生类对象的初始化由基类和派生类共同完成:
- 基类的数据成员由基类的构造函数初始化
- 派生类的数据成员由派生类的构造函数初始化
- 当创建派生类的对象时:
- 先执行基类的构造函数,再执行派生类构造函数。
- 默认情况下,调用基类的默认构造函数,如果要调用基类的非默认构造函数,则必须在派生类构造函数的成员初始化表中指出。
- 如果一个类D既有基类B、又有成员对象类M:
- 在创建D类对象时,构造函数的执行次序为: B(调用顺序按照各个基类被继承时声明的顺序)->M->D类构造函数体
- 当D类的对象消亡时,析构函数的执行次序为:D->M->B
- 对于拷贝构造函数:
- 派生类的隐式拷贝构造函数(由编译程序提供)将会调用基类的拷贝构造函数。
- 派生类自定义的拷贝构造函数在默认情况下则调用基类的默认构造函数。需要时,可在派生类自定义拷贝构造函数的“基类/成员初始化表”中显式地指出调用基类的拷贝构造函数。
②赋值
- 如果派生类没有提供赋值操作符重载,则系统会为它提供一个隐式的赋值操作符重载函数,其行为是:
- 对基类成员调用基类的赋值操作进行赋值,
- 对派生类的成员按逐个成员赋值。
- 派生类自定义的赋值操作符重载函数不会自动调用基类的赋值操作,需要显式地调用基类的赋值操作符来实现基类成员的赋值。
- 派生类不从基类继承赋值操作
③聚集
- 继承不是代码复用的唯一方式,有些代码复用不宜用继承来实现。
- 类之间还存在一种聚集(aggregation,也称聚合)关系:
- 一个类作为另一个类的成员对象类。
- 具有聚集关系的两个类之间属于部分与整体的关系(is-a-part-of)
④子类型
- 子类型关系可以传递,但是不可逆
(三)消息(成员函数调用)的动态绑定
1.静态绑定
- 默认静态绑定
2.动态绑定
- 一般情况下,需要在func1(或func2)中根据x(或p)实际引用(或指向)的对象来决定是调用
A::f
还是B::f
。即,采用动态绑定。在C++中用虚函数来实现动态绑定。
- 基类中的一个成员函数如果被定义成虚函数,则在派生类中定义的、与之具有相同型构的成员函数是对基类该成员函数的重定义(或称覆盖,override)
相同的型构是指:派生类中定义的成员函数的名字、参数类型和个数与基类相应成员函数相同,其返回值类型与基类成员函数返回值类型或者相同,或者是基类成员函数返回值类型的派生类。
- 一旦在基类中指定某成员函数为虚函数,那么,不管在派生类中是否给出
virtual
声明,派生类(以及派生类的派生类...)中与其有相同型构的成员函数均为虚函数。
- 只有类的成员函数才可以是虚函数,但静态成员函数不能是虚函数。
- 构造函数不能是虚函数,析构函数可以(往往)是虚函数。
- 只有通过基类的指针或引用访问基类的虚函数时才进行动态绑定。
- 基类的构造函数中对虚函数的调用不进行动态绑定。
3.纯虚函数和抽象类
- 纯虚函数是指函数体为空(=0)的虚函数
- 包含纯虚函数的类称为抽象类
- 抽象类不能用于创建对象。
- 抽象类的作用是为派生类提供一个基本框架和一个公共的对外接口
(四) 多继承
- 多继承是指派生类可以有一个以上的直接基类。
1.定义
- 定义格式:
- 继承方式及访问控制的规定同单继承。
- 派生类拥有所有基类的所有成员。
- 基类的声明次序决定:
- 对基类构造函数/析构函数的调用次序
- 对基类数据成员的存储安排。
2.命名冲突
- 解决方法:基类名受限
3.重复继承----虚基类
- 若直接基类有公共的基类,则会出现重复继承
例:
- Author:Rainnn
- URL:https://blog.rainnn.top//article/c%2B_notes
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!