C++ 期末复习 2024

  • 本科生学业指导中心

    • 逸夫楼 201

  • 王尧勇

    • 哔哩哔哩 王尧勇

    • 计算机科学与工程学院 2020级 计算机科学与技术专业

0. 前言

  • 编程语言是人类定义的规则,如果制定 C++ 规则的人的思维是正常的,那么 Ta 的这些规则大抵应该是符合逻辑的。

  • 于是我们可以从规则制定者的角度去审视编程语言的语法,即设计这个东西出来是用来干什么的?

    1. 这个语法可以做到之前的语法做不到的事情(比如结构控制语句)。

    2. 这个语法可以更加方便地做到之前语法做起来很麻烦的事情(比如函数)。

    3. 这个语法是为了弥补之前某个语法造成的问题。

1. 基本类型和变量

1.0 关键字和标识符

  • 关键字就是语言本身要用到的一些名字,本身表示了某种功能,因此不能再用它们来命名。

  • 标识符,就是某个东西的名字,最常见的就是变量的命名,遵循以下规则:

    • 字母、数字、下划线 组成

    • 数字不能作为第一个字符

    • 不能和关键字一样

  • 注意,关键字严格按照课本所列举的记忆,cincout 不是关键字,defineinclude 不是关键字,等等。

1.1 基本类型

5种基本类型:布尔型(bool)、字符型(char等)、整型(int等)、浮点型(double等)、空类型(void)。

类型,就是对内存中的 01 串的不同角度的诠释。

布尔型

  • 布尔型直接 cout 会输出数字,如果要输出 truefalse,则 cout << boolalpha <<

  • 布尔型转整型:true1false0

  • 整型转布尔型:0false,非 0true

整型

  • 整数罢了,但是存的都是补码的形式

  • 取反加一?都什么年代,还在算传统补码😋

X补码=2nXX_{补码} = 2^n - |X|

例如,对于 8 位的整型,-3 在内存中是以补码存储,也就是 256 - | -3 | = 253 = 1111 1101

浮点型

  • 浮点数存储原理和整数很不一样(IEEE 754)

  • 存在误差,可能十进制下的有限小数在二进制下是无限循环的,因此可能会有精度的损失

  • 因此判断一个浮点数是否等于某个数不能直接用 == 判断

字符型

  • 字符型在内存中就是按照整数存储的

  • 整数和字符的映射规则就是 ASCII 码

  • 课本中和考试中,char 都是默认有符号的,因此,如果你见到一些故意找茬的赋值语句:

1.2 字面值

  • 字面值的含义就是其字面意义:字面上的值😅

  • 整型值的不同进制表示:无前缀默认为10进制、数字0开头表示8进制、0x或者0X开头表示16进制、0b或0B开头表示2进制。

  • 浮点值的定点表示法:不加后缀默认为double、加后缀f或F为float、加后缀l或者L为long double

  • 浮点值的科学表示法:尾数e指数尾数E指数,注意 e 后面只能是整数

  • 转义字符

    • 转义字符的主要目的是为了表示键盘上不方便敲进代码的键位,例如‘\n’‘\t’

    • 另外转义字符也可以用ASCII码来表示字符,但只能用8进制或16进制,8进制0开头例如'\023'、16进制x开头例如'\xa'

  • 转义字符的功能超乎你想象

ANSI Escape Code

1.3 变量

  • 变量初始化

  • auto 自动推导

  • const 命名常量

2. 运算符和表达式

2.0 杂项

  • 表达式没有分号,有分号的是语句捏

  • 运算符优先级

    • 考前背就完事了,真记不住所有的

    • 优先结合并非优先计算

  • 表达式的返回值

2.0 算数运算符

  • 单目运算符:- 负数运算符,+ 正数运算符;

  • + 加、- 减、* 乘;

  • / 除:除数不能为 0,整型和整型运算结果为整型;浮点数运算,结果为浮点数;

  • a%b 取模:b 不能为 0,结果的符号和 a 一致,运算变量不能是浮点数

  • 开平方使用 sqrt函数(cmathmath.h 头文件),返回值类型为 double;

  • 没有数学上的 $a^b$ 运算符,可以使用 cmath 中的 pow 函数,不要用 ^,这是异或捏😫

2.1 关系运算符

  • 6个关系运算符:<<=>>===!=;

  • 结果为布尔类型

  • 当两个参与比较的左右操作数类型不同时,自动转换为更高精度的类型再比较

  • == 不要写成 = 😅

2.2 逻辑运算符

  • 3个逻辑运算符:! 逻辑非、&& 逻辑与、|| 逻辑或;

  • !的优先级高于算数运算符,&&||的优先级低于算术运算符和关系运算符;

  • &&||的短路特性

  • 不考虑优先级?🧐

2.3 条件比较运算符

  • 条件运算符

<表达式1>为真则求解<表达式2>,其值作为整个式子的返回值,<表达式3>不会执行

<表达式1>为假则求解<表达式3>,其值作为整个式子的返回值,<表达式2>不会执行

  • 比较前,当<表达式2>和<表达式3>类型不同时,自动转换为更高精度的类型

2.4 位运算

  • << 左移:右边补0,带符号数溢出可能会改变符号

  • >> 右移:带符号数左边补符号数,无符号数左边补0

  • 若移动位数为负数,VS和DEV C++会警告,最后结果为0

  • 移动位数不要超过变量的位数,否则按取模算

  • ~ 取反、& 按位与、| 按位或、^ 按位异或

  • 优先级:取反 > 按位与 > 按位异或 > 按位或

  • 操作数必须是整型

  • 位运算均针对补码

2.5 赋值运算符

  • 优先级很低(仅高于逗号运算符)

  • 结合性自右向左:a=b=c;相当于a=(b=c);

  • 赋值运算符左边必须是左值,赋值运算符的返回值也是左值,即可以(a=5)=6;

2.6 逗号运算符

  • 优先级最低🤣🤣🤣

  • 从左到右计算,最右边的表达式作为整个式子的值

  • 若最右边是一个左值,逗号表达式的值也是左值

  • 坑😡

2.7 自增自减运算符

以自增运算为例。

  • 前置 i++

    1. +1

    2. 返回 变量i 的值

  • 后置 ++

    1. j = i

    2. i = i+1

    3. 返回 j

  • ++的优先级高于所有算术运算符,且后置高于前置

  • ++必须是左值,前置++返回左值,后置++返回右值

  • 自增和加法一起用容易混淆,甚至引发错误,不要这么干

  • 同时自增运算很容易出现未定义行为

链接

2.8 sizeof

  • sizeof 是一个运算符,不是一个函数!

  • sizeof(类型名)sizeof(表达式),前者编译时计算完毕,后者运行时计算;

  • sizeof(指针)得到的是指针变量的大小,例如32位系统大小为4,64位系统大小为8;

  • sizeof(数组名)得到的整个数组所占字节数,再除以sizeof(数组元素类型)可得数组元素个数;

2.9 类型转换

  • 整型提升

  • 强制类型转换

  • 类型转换可以深究很多,好在考试不会这么难

2.10 typeiddecltype

3. 基本语句

3.0 if

3.1 switch

  • 如果正常人写出来的 switch 语句,不会有什么难度,然而考试考的都是不正常的😅

    • case中无break

    • default 不在最下面

  • casedefault 只决定从哪里开始执行,一旦确定之后,便是从上往下执行,直到执行完或者遇到 break

3.2 while

3.3 for

  • 什么B题目啊😅

3.4 跳转语句

  • break

  • continue

  • goto

4. 函数

4.0 函数和主函数

  • main 函数的参数

4.1 值传递、引用传递、指针传递

  • 值传递

  • 为什么没有交换,怎么会事捏?🤨

  • 引用传递

  • 指针传递(地址传递)

  • 指针传递的坑

4.2 函数重载

  • 讨论函数重载时,把自己想象为编译器,可爱的小编译器一枚吖😘

  • 形参个数和类型不同就可以重载

  • 核心在于让编译器知道是两个不同的函数,在传入参数的时候不会混淆

  • 返回值类型不同不能用来重载

  • 有默认值的情况可能会有干扰:

  • const修饰不能作为重载的依据(仅限这种最简单的情况下,涉及到指针和引用的const就复杂了)

  • 重载函数调用时的最佳匹配:

    1. 严格匹配

    2. 整型提升

    3. 赋值转换

    4. 有多个形参时情况复杂,核心还是没有二义性;

  • 函数重载的类型匹配很复杂,好在考试不会涉及到更复杂的(应该吧)

4.3 默认参数

  • 必须优先保证右边的参数有默认值,不能出现左边有,而右边没有的情况

4.4 变量作用域

  • 每当使用变量时,使用的是最近的还存活着的那个变量

4.5 变量类型

  • 平平无奇的非静态局部变量

  • 静态局部变量:生命周期是全程的,但只能在函数内使用

  • 孟婆汤掺水了

  • 静态全局变量:单个文件内部共享

  • 非静态全局变量:多文件共享

  • extern修饰

4.6 函数的递归

  • 函数可以调用函数,函数可以调用自己

  • 盗梦空间是吧

4.7 可变参数

4.8 constexpr 函数

  • 编译时期确定值

  • 只要在编译时、运行前能知道函数的结果,那就不会报错

5. 编译预处理

  • 宏命令末尾没有分号

5.0 文件包含

  • #include <filename> 或者 #include "filename"

  • 纯纯的复制的效果

5.1 无参宏

  • 作用域是定义的那一行以下直到文件末尾

  • 宏定义和变量重名

  • 子字符串不会被替换

5.2 有参宏

  • 十分机械的字符串替换

6. 结构体、枚举、联合体

6.0 结构体

  • 在 C++ 中,结构体和类唯一的区别就是默认的访问修饰符不一样,结构体默认 public,类默认 private,除了这一点,其他的特性完全一样。

  • 常见考题

6.1 结构体的字节对齐

6.2 结构体的嵌套定义

  • 不能嵌套定义自己

  • 但是嵌套自己的指针可以(链表的节点)

6.3 位域

6.4 枚举

  • 枚举类型的定义

不指定值,其值就是前一个+1

指定值,就是指定的值

第一个如果不指定值,那就是 0

  • 枚举变量的定义

  • 枚举 和 int 的转换

    • 枚举常数会隐式转换成 int,可以把枚举值赋值给 int 变量

    • 但是反过来不会,所以不能把一个 int 值赋值给枚举变量

    • 枚举值参与算术运算时隐式转换为 int 参与运算,结果也不再是枚举类型

  • 典中典的一道题

6.5 强类型枚举

6.6 联合体

  • 百变怪罢了

6.7 typedefusing

7. 指针

7.0 一些说明

  • 学习指针,至少从备考的角度来说,要更多的关注 怎么用,而非更多关注 是什么

  • 说明几个概念,下文中都是表示如下意思:

    • 指针:指的是各种指针变量

    • 地址:指的是 0x12356ff 这样的值,指针里面存的就是地址

  • 学习一个指针(或者一个类似指针的东西,比如数组名),我们只要关心其以下几个特点:

    1. 级别

    2. 指向类型

    3. 自身指向的可变性

    4. 指向的内容的可变性

7.1 内存和地址

  • 指针相关的题,画好图就可以帮助解题

7.2 指针和多级指针

  • 为什么需要指针?

    • 为了考试难

    • 灵活,太灵活了

      • 动态开辟内存

      • 控制数组、控制函数、控制变量

      • 跨作用域

  • 指针为什么分这么多不同类型?

    • 在语法层面进行区分,不然都是 void* 不好读

    • 指针加减法时的实际地址变化不一样

  • 指针的定义

  • 多级指针

    • 套娃思想

    • 指针其实就是个用来存地址的变量,那么指针变量本身也有对应的内存地址

    • 变量的地址可以被指针变量存储,即指针可以指向一个普通变量,那么也可以有一种变量用来存储指针变量的地址,即二级指针

7.3 数组

  • 数组定义

  • 数组大小只能为正整数常量(包括const常量、C++11中constexpr函数),实际使用时发现编译器也允许用变量定义数组大小,但这不好🥵

  • 数组初始化

  • 对部分元素初始化,后面没初始化的默认为0

  • 对所有元素赋值,可不指定长度,根据所给个数确定

  • 定义时没初始化,之后就不能用大括号初始化了

  • 访问数组时注意下标为0~n-1,下标越界语法上没有错误,会访问到未知的内存

  • 数组和数组不能整体赋值

  • 不要直接比较数组名来判断两个数组是否相等(实际上在比较地址)

以下方式对str的声明中,不能将str作为字符串使用的是:

  • 二维数组

    • 二维数组可以看成一维数组的数组

    • 二维数组的初始化:核心:列宽

  • 实际存储是一维的

7.4 指针和数组

  • 数组在很多时候,表现得像一个指针,但是它不是指针,不是指针

  • 之所以这样,是很多时候我们使用数组的时候,它很容易就隐式转换成指针类型了

注:本人仅使用这张图代表的梗来和数组与指针类比,达到一种幽默诙谐的教学作用,并不代表本人认同或者不认同原图代表的观点和言论。

  • 数组是一个常量,值不能被改变值不能被改变值不能被改变

  • 一级指针可以指向一个数组,就是指向数组首元素,把一个数组赋值给指针,是隐式转换为了常量指针赋值给指针

  • 下标访问 和 解引用 等价

  • 当然了,还是有点差别,这俩优先级不一样,[] 优先级高于 *

  • 解引用和自增自减运算

若有int a[]{1, 3, 5, 7, 9}, *p = a+2;,则执行表达式int b = 1 + (*p++);后,b*p的值为多少?

A. 6 6 B. 6 7 C. 7 6 D. 8 7

  • 二维数组也同样表现得像一个二级指针常量,其指向不能被改变

  • 上面提到可以把数组视作指针常量来做题,但在以下情形中,二者有区别

    • sizeof运算符

    • & 取地址符

7.5 指针数组和数组指针

7.5.0 指针数组

  • 数组的元素是丰富多彩的,可以是基本类型。可以是结构体,可以是对象,那当然也可以是指针

  • 指针数组 就是一个普通的数组,只不过它的每个元素都是指针类型

  • 而数组本身又表现得像一个常量指针,故它可以看作和二级指针同级别

7.5.1 数组指针

  • 是一个指针

  • 指向数组的指针

  • 用于指向一个特定列宽的二维数组,其级别和二维数组相似

  • 在数组指针指向p之后,即可用p访问二维数组a

若有

++*(*++p)的值为多少?

7.6 二级指针总结

自身的可变性
指向的内存的可变性

int** p;

Y

Y

int a[2][3];

N

N

int (*p)[4];

Y

N

int *b[4];

N

Y

7.7 字符数组和字符串

  • C++基本类型中没有字符串,用字符数组字符指针的形式来表示字符串(当然还有string)

  • 第一行是定义了一个字符数组,可以存字符串,最大长度为10(包括了最后的\0

  • 第二行是定义了一个二维数组,也可以看做一个字符串的一维数组,可以存4个字符串,每个字符串最大长度是20(包括了最后的\0

  • 字符数组的初始化

7.8 字符指针

  • 字符指针本身只有一个指针变量的大小,可以指向一个常量字符串

  • 或者指向一个已有的字符数组

7.9 字符数组和字符指针

  • 字符二维数组和字符指针数组

若有char *s1 = "C++";char s2[] = "C++";,则下面选项不会导致错误的是:

7.10 字符串的输入输出

  • cin或者cin.get,利用循环,一个字符一个字符读入,其中cin会跳过空格一类的字符,cin.get可以读取空格、制表符、换行符;

  • cin>>str;直接将一个个字符读入str所在内存,末尾自动加\0cin会被空格、制表符、换行符截断;

  • cin.getline(str, 10);读入字符串,可读入空格、制表符,遇到回车结束;

  • 只有指向变量的指针或者数组名才可以输入;

  • cout << 字符指针或字符数组,从给定位置一直输出字符直到遇到\0

7.11 字符串处理函数

  • 字符串处理函数在头文件<cstring><string.h>)中;

  • strlen(str),返回int类型的字符串长度(不包括\0),str可以使字符数组,也可以是字符指针,也可以是字符串常量;

  • strcpy(s1, s2),返回值指向第一个参数所指位置的字符指针,从位置s1开始,把s2开始的字符串一个一个拷贝过来,s1必须是指向可修改的变量内存的,注意数组越界问题

  • strcat(s1, s2),返回指向第一个参数所指位置的字符指针,将字符串s2拼接到s1后面,s1必须是指向可修改的变量内存的,注意数组越界问题;

  • strcmp(s1, s2),返回一个int整数,从前往后逐个位置比较s1s2中对应位置的字符的ASCII码的值,若s1大于s2,则返回值大于0,若s1小于s2,则返回值小于0,相等则返回值为0;

  • VS中提供了strcpy_sstrcat_s的安全版本;

7.12 指针与函数

7.12.0 指针作为函数形参

  • 指针作为函数形参可以在不同函数之间传递变量

  • 一维数组名作为函数形参,会退化为一级指针

  1. 一级指针等价于一维数组

  2. 二级指针等价于指针数组

  3. 数组指针等价于二维数组

下面哪一个函数能接受处理任意行、任意列的int矩阵,row表示行数,col表示列数:

若有

1.以下选项不能作为实参调用函数void f(int (*p)[4])的是:

A. &a

B. &b

C. &c[0]

D. c

2.以下选项能作为实参调用函数void f(int **)的是:

A. &a

B. &b

C. c

D. d

7.12.1 指针作为函数返回值

  • 不能返回局部非静态变量

  • 返回外部传进来的指针

  • 返回局部静态变量

  • 返回全局变量

  • 返回动态开辟的内存

7.12.2 函数指针

  • 函数指针用于指向某一类函数

  • typedef或using起别名

设有语句

下面哪一个定义了返回值为int型指针的函数?

7.13 动态分配内存 newdelete

  • new返回一个指向开辟出的内存的指针,若开辟了一个数组,则是指向首元素的指针

下面哪一条语句是正确的?

  • 若创建的是二维数组,则返回值类型为数组指针

  • new出来的内存在堆上,不在当前函数的栈内存上,不会自动回收

  • delete可以手动删除new出来的指针

7.14 const 指针、void 指针、空指针、野指针

7.14.0 const 指针

  • const默认修饰左边,除非左边没有或者左边已经被修饰

  • const指针的重载问题:之前在函数重载中提到,const不能作为重载的依据,即一个形参变量是不是const修饰,不作为形参的区别,但是指针这里有例外

7.14.1 void指针

  • void指针可指向任何类型的变量、函数

  • 通过强制类型转换,void指针可以控制许多变量,进行通用性设计

7.14.2 空指针和野指针

  • 指针可以不指向任何地方,赋值为nullptr即可(或NULL

  • 野指针:被 delete 了的指针(这种也叫 悬垂指针)或者没有初始化赋值的局部变量指针,我们不知道它指向了哪里🤨

8. 引用

8.0 左值引用

  • 引用就是别名

  • 可以把引用看作功能阉割的指针,灵活性减小,但是使用更加方便了,但引用不是指针

  • 之后对ra的所有操作就相当于对a的操作

  • const修饰的左值引用可以引用字面值或者右值表达式

8.1 引用与函数

  • 引用作为函数形参,可以传递变量

  • 引用作为函数返回值,和指针作为函数返回值类似``

  • 返回动态开辟的内存的引用,注意用完之后delete

  • 函数的引用

  • 函数引用可作为函数的返回值

8.2 引用与数组

  • 和指针不同的是,数组的元素不能是引用

  • 可以有数组的引用,但数组的引用不能作为函数的返回值

8.3 引用与指针

  • 引用的指针就是被引用变量的指针

  • 指针的引用

  • 没有引用的引用,芝士右值引用

  • 没有空引用

8.4 & 符号

  • 到目前为止,已经接触了 & 的三种用法,注意不要混淆

    1. &在位运算中代表按位与

    2. &表示取地址操作

    3. &可以用来定义 左值引用

9. 链表

  • 链表结点的结构:存值的变量,存后继的指针

  • 处理链表的函数中,通常都是会判断是否是头结点,并做特殊处理,或者判断头结点是否为空;

  • 通常需要两个指针来处理链表,一个用来指向新的结点,一个用来指向链表的某个位置;

  • 遍历链表时,通常会出现p = p->next;这样的语句;

1.已知一个单向链表结点的数据结构为:

函数Node* AddNode (Node *head, int n)的功能是:新建结点使之数据为n,将该结点处插入到head指向的单向链表中,放在数据与n差距最小的结点(如有多个最小差距结点,则考虑第一个)之后,并返回链头结点地址。若为空链,则新建结点直接作为头结点。

在程序空格位置填入合适的代码,完成AddNode函数。

2.在精准扶贫的大背景下,在政府和企业的帮助下,小明开了个养猪场。该养猪场有若干猪圈,我们用一个单向链表用于保存猪圈中猪的相关信息,其结点的数据结构为:

函数double Sell(Pig *&head, double threshold, double price)的功能是:将体重超过threshold斤的猪出笼,以price元/斤的价格售出并返回总售价。具体操作为:将链表中weight超过threshold的结点全部删除,同时根据price元/斤的价格计算并返回总售价。

在程序空格位置填入合适的代码,完成Sell函数。

Last updated