王尧勇
计算机科学与工程学院 2020级 计算机科学与技术专业
0. 前言
编程语言是人类定义的规则,如果制定 C++ 规则的人的思维是正常的,那么 Ta 的这些规则大抵应该是符合逻辑的。
于是我们可以从规则制定者的角度去审视编程语言的语法,即设计这个东西出来是用来干什么的?
这个语法可以做到之前的语法做不到的事情(比如结构控制语句)。
这个语法可以更加方便地做到之前语法做起来很麻烦的事情(比如函数)。
1. 基本类型和变量
1.0 关键字和标识符
关键字就是语言本身要用到的一些名字,本身表示了某种功能,因此不能再用它们来命名。
标识符,就是某个东西的名字,最常见的就是变量的命名,遵循以下规则:
注意,关键字严格按照课本所列举的记忆,cin
、cout
不是关键字,define
、include
不是关键字,等等。
1.1 基本类型
5种基本类型:布尔型(bool
)、字符型(char
等)、整型(int
等)、浮点型(double
等)、空类型(void
)。
类型,就是对内存中的 01
串的不同角度的诠释。
布尔型
布尔型直接 cout
会输出数字,如果要输出 true
或 false
,则 cout << boolalpha <<
Copy #include <iostream>
using namespace std;
bool flag = true;
cout << flag << "\t" << !flag << "\n";
cout << boolalpha << flag << "\t" << !flag << "\n";
布尔型转整型:true
为 1
,false
为 0
整型转布尔型:0
为 false
,非 0
为 true
整型
例如,对于 8
位的整型,-3
在内存中是以补码存储,也就是 256 - | -3 | = 253 = 1111 1101
浮点型
存在误差,可能十进制下的有限小数在二进制下是无限循环的,因此可能会有精度的损失
因此判断一个浮点数是否等于某个数不能直接用 == 判断
Copy double x = 2.0 + 1.14;
if(x == 3.14) {
cout << "nya\n";
}
Copy #include <cmath>
double x = 2.0 + 1.14;
if(fabs(x - 3.14) < 1e-5) {
cout << "nya\n";
}
字符型
Copy cout << "A : " << int('A') << endl;
cout << "a : " << int('a') << endl;
cout << "0 : " << int('0') << endl;
Copy char str[] = "114514";
int num = 0;
for(int i = 0; str[i] != '\0'; i++) {
num = num * 10 + (str[i] - '0');
}
cout << num << endl;
课本中和考试中,char
都是默认有符号的,因此,如果你见到一些故意找茬的赋值语句:
Copy char a = 0;
char b = -128;
unsigned char c = -159;
unsigned char d = -1;
cout << (int)a << endl << (int)b << endl << (int)c << endl << (int)d << endl;
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'
Copy #include <cstring>
const char *str = "abcd\'efg\028";
cout << strlen(str) << endl;
cout << sizeof(str) << endl;
Copy cout << "\033[30m 黑色\n";
cout << "\033[31m 红色\n";
cout << "\033[32m 绿色\n";
cout << "\033[33m 黄色\n";
cout << "\033[34m 蓝色\n";
cout << "\033[35m 紫色\n";
cout << "\033[36m 青色\n";
cout << "\033[37m 白色\n";
1.3 变量
Copy int a = 114; // 传统初始化, 不做安全检查
int b(514); // 对象式初始化, 不做安全检查
int c{19}; // C++11新标准, 有安全检查
Copy auto a = 3.14;
auto b = 3.14f;
auto c = 3.14l;
auto d = 3;
auto e = false;
auto f = 'x';
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
cout << typeid(e).name() << endl;
cout << typeid(f).name() << endl;
Copy auto p1 = new double;
auto *p2 = new double(2.33);
auto p3 = new double[3]{0, 1, 2};
auto p4 = *new double[3]{0, 1, 2};
cout << typeid(p1).name() << endl;
cout << typeid(p2).name() << endl;
cout << typeid(p3).name() << endl;
cout << typeid(p4).name() << endl;
Copy int const a = 114;
const int b = 514;
2. 运算符和表达式
2.0 杂项
Copy int x = 2 + 3 + 4 * 5;
2.0 算数运算符
/
除:除数不能为 0,整型和整型运算结果为整型;浮点数运算,结果为浮点数;
Copy double r;
cin >> r;
double s = 1/2*r*r;
cout << s;
a%b
取模:b 不能为 0,结果的符号和 a 一致,运算变量不能是浮点数
Copy cout << 5 % 3 << "\n";
cout << -5 % 3 << "\n";
cout << 5 % -3 << "\n";
开平方使用 sqrt
函数(cmath
或 math.h
头文件),返回值类型为 double;
没有数学上的 $a^b$ 运算符,可以使用 cmath
中的 pow
函数,不要用 ^
,这是异或捏😫
2.1 关系运算符
当两个参与比较的左右操作数类型不同时,自动转换为更高精度的类型再比较
Copy int c = -4;
unsigned a = 2;
cout << boolalpha << (c<a) << endl;
Copy int x;
cin >> x;
while(x = 3) {
cout << "Ciao\n";
x++;
}
2.2 逻辑运算符
3个逻辑运算符:!
逻辑非、&&
逻辑与、||
逻辑或;
!
的优先级高于算数运算符,&&
和||
的优先级低于算术运算符和关系运算符;
Copy int a = 2, b = 3;
int c = a > b && b++;
int d = a < b || a++;
cout << a << " " << b << " " << c << " " << d << endl;
Copy int a = 3, b = 2;
b == 2 || a++;
2.3 条件比较运算符
Copy <表达式1> ? <表达式2> : <表达式3>
<表达式1>为真则求解<表达式2>,其值作为整个式子的返回值,<表达式3>不会执行
<表达式1>为假则求解<表达式3>,其值作为整个式子的返回值,<表达式2>不会执行
Copy int a = 2, b = 3;
int c = a > b ? a++: b++;
cout << a << " " << b << " " << c << endl;
比较前,当<表达式2>和<表达式3>类型不同时,自动转换为更高精度的类型
Copy unsigned int a = 2;
int b = -1;
auto c = false ? a : b;
cout << c;
2.4 位运算
Copy int a = -34359738367;
int b = a << 1;
cout << b;
>>
右移:带符号数左边补符号数,无符号数左边补0
若移动位数为负数,VS和DEV C++会警告,最后结果为0
优先级:取反 > 按位与 > 按位异或 > 按位或
Copy int a = -3 ^ 5;
cout << a;
2.5 赋值运算符
结合性自右向左:a=b=c;
相当于a=(b=c);
赋值运算符左边必须是左值,赋值运算符的返回值也是左值,即可以(a=5)=6;
Copy int a = 10, b;
a = b = 1;
2.6 逗号运算符
Copy auto x = (3, 4+7, 3*5, 233);
cout << x;
Copy int a = (cout << "1 ! ", cout << "5 !", 15);
Copy int a = 2, b = 3, c;
c = a+b, a-b, a*b;
cout << c;
2.7 自增自减运算符
以自增运算为例。
++必须是左值,前置++返回左值,后置++返回右值
自增和加法一起用容易混淆,甚至引发错误,不要这么干
Copy int a = 3;
int b = ++a + ++a;
cout << b;
2.8 sizeof
sizeof(类型名)
或sizeof(表达式)
,前者编译时计算完毕,后者运行时计算;
sizeof(指针)
得到的是指针变量的大小,例如32位系统大小为4,64位系统大小为8;
sizeof(数组名)
得到的整个数组所占字节数,再除以sizeof(数组元素类型)
可得数组元素个数;
2.9 类型转换
Copy y = double(a)/b;
y = (double)a/b;
2.10 typeid
、 decltype
3. 基本语句
3.0 if
3.1 switch
如果正常人写出来的 switch
语句,不会有什么难度,然而考试考的都是不正常的😅
case
和 default
只决定从哪里开始执行,一旦确定之后,便是从上往下执行,直到执行完或者遇到 break
Copy int a = 0;
while(a < 5) {
switch(a) {
case 0:
cout << "地振高冈\n";
a++;
case 1:
cout << "一派溪山千古秀\n";
a += 2;
default:
cout << "门朝大海\n";
case 2:
cout << "三河合水万年流\n";
break;
case 3:
cout << "天父地母\n";
a++;
case 4:
cout << "反清复明\n";
a++;
}
}
3.2 while
3.3 for
Copy int i = 0;
cout << "hello";
for(i = 0; i <= 5; i++);
{
if(i != 6)
cout << i;
}
3.4 跳转语句
Copy #include <iostream>
using namespace std;
int main() {
http://www.baidu.com
return;
}
4. 函数
4.0 函数和主函数
Copy #include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
cout << argc << endl;
for(int i = 0 ; i < argc; i++) {
cout << argv[i] << endl;
}
return 0;
}
4.1 值传递、引用传递、指针传递
Copy #include <iostream>
using namespace std;
void mySwap(int a, int b) {
int t = a; a = b; b = t;
}
int main() {
int a = 2, b= 3;
mySwap(a, b);
cout << a << " " << b;
return 0;
}
Copy #include <iostream>
using namespace std;
void mySwap(int &a, int &b) {
int t = a; a = b; b = t;
}
int main() {
int a = 2, b= 3;
mySwap(a, b);
cout << a << " " << b;
return 0;
}
Copy #include <iostream>
using namespace std;
void mySwap(int *p, int *q) {
int t = *p; *p = *q; *q = t;
}
int main() {
int a = 2, b= 3;
mySwap(&a, &b);
cout << a << " " << b << endl;
return 0;
}
Copy void f(int *s) {
s = new int;
*s = 10;
}
int main() {
int a = 5;
int *s = &a;
f(s);
cout << a << endl;
return 0;
}
Copy void swap(int *p, int *q) {
int *t = p;
p = q;
q = t;
}
int main() {
int a = 2, b= 3;
int *p = &a, *q = &b;
swap(p, q);
cout << *p << " " << *q << endl;
return 0;
}
Copy void swap(int* &p, int* &q) {
int *t = p;
p = q;
q = t;
}
4.2 函数重载
讨论函数重载时,把自己想象为编译器,可爱的小编译器一枚吖
😘
核心在于让编译器知道是两个不同的函数,在传入参数的时候不会混淆
Copy int f(int a);
int f(int a, int b);
int f(double a);
f(3)
f(2, 3)
f(3.14)
Copy int f(int a);
int f(int a, int b = 3);
f(3)
const
修饰不能作为重载的依据(仅限这种最简单的情况下,涉及到指针和引用的const就复杂了)
Copy int f(int a);
int f(const int a);
Copy void f(double a) {
cout << a*a;
}
void f(int a) {
cout << a + a;
}
void f(char a) {
cout << a;
}
f('a')
函数重载的类型匹配很复杂,好在考试不会涉及到更复杂的(应该吧)
4.3 默认参数
Copy int f(int a = 1, int b = 2, int c = 3);
Copy int f(int a = 1, int b, int c);
Copy int f(int a = 1, int b, int c = 3);
Copy int f(int a, int b = 2, int c);
Copy int f(int a = 1, int b = 2, int c);
必须优先保证右边的参数有默认值,不能出现左边有,而右边没有的情况
4.4 变量作用域
Copy #include <iostream>
using namespace std;
int a = 233;
void f() {
int a = 666;
cout << a << endl;
}
int main() {
int a = 3;
cout << a << endl;
{
int a = 5;
cout << a << endl;
}
f();
cout << ::a << endl;
return 0;
}
4.5 变量类型
Copy void f() {
int a = 3;
cout << a << endl;
++a;
}
int main() {
f();
f();
f();
return 0;
}
静态局部变量:生命周期是全程的,但只能在函数内使用
Copy void f() {
static int a = 1;
a++;
cout << a;
}
int main() {
f();
f();
f();
return 0;
}
Copy int a; // a有初始值0
extern int b = 3; // 必须显式初始化
4.6 函数的递归
Copy #include <iostream>
using namespace std;
int f(int n) {
if(n == 1)
return 1;
if(n == 2)
return 1;
return f(n-1) + f(n-2);
}
int main() {
cout << f(5);
return 0;
}
Copy #include <iostream>
using namespace std;
int f(int a[], int s, int e) {
if(s == e)
return a[s];
return a[s] + f(a, s+1, e);
}
int main() {
int a[] = {1, 2, 3, 4, 5, 6, 7};
cout << f(a, 2, 5);
return 0;
}
4.7 可变参数
Copy int f(int a, ...) {
}
4.8 constexpr
函数
只要在编译时、运行前能知道函数的结果,那就不会报错
Copy constexpr int f(int n) {
if(n == 0)
return 0;
if(n == 1)
return 1;
return f(n-1) + f(n-2);
}
f(3);
const int m = 4;
f(m);
constexpr int m = 4;
f(m);
int n;
cin >> n;
f(n);
5. 编译预处理
5.0 文件包含
#include <filename>
或者 #include "filename"
Copy #include <iostream>
using namespace std;
int main() {
int a[] = {
#include "data.txt"
};
for(int i = 0; i < 5; ++i) {
cout << a[i] << " ";
}
return 0;
}
5.1 无参宏
Copy #include <iostream>
using namespace std;
int main() {
double PI = 3.14;
cout << PI << endl;
#define PI 3
cout << PI << endl;
return 0;
}
Copy #define 华为 苹果
cout << "刘德华为什么不演坏人";
#define av xx
cout << "java";
5.2 有参宏
Copy #include <iostream>
#define F(a) a * a
int main() {
int a = 2, b = 3, c = 4;
int d = F(b - a)*c;
cout << d;
return 0;
}
6. 结构体、枚举、联合体
6.0 结构体
在 C++ 中 ,结构体和类唯一的区别就是默认的访问修饰符不一样,结构体默认 public
,类默认 private
,除了这一点,其他的特性完全一样。
Copy struct s {
int n;
int *m;
};
int d[3] = {10, 20, 30};
s arr[3] = {100, &d[0], 200, &d[1], 300, &d[2]}, *p = arr;
cout << ++(p->n) << " ";
cout << (++p)->n << " ";
cout << *++(p->m);
6.1 结构体的字节对齐
6.2 结构体的嵌套定义
Copy struct A {
int a;
struct B{
int b;
};
B c;
};
Copy struct A {
int a;
struct B{
int b;
} c;
};
Copy struct A {
int n;
A b;
};
Copy struct A {
int n;
A *next;
};
6.3 位域
6.4 枚举
Copy enum Weekday {Mon, Tue=3, Wed, Thur, Fri, Sat,Sun};
不指定值,其值就是前一个+1
指定值,就是指定的值
第一个如果不指定值,那就是 0
Copy enum Weekday {Mon, Tue, Wed, Thur, Fri, Sat,Sun};
Weekday week = Mon;
int i = Mon;
i = week;
week = i;
week = (Weekday)i;
枚举 和 int
的转换
枚举常数会隐式转换成 int
,可以把枚举值赋值给 int
变量
但是反过来不会,所以不能把一个 int
值赋值给枚举变量
枚举值参与算术运算时隐式转换为 int
参与运算,结果也不再是枚举类型
Copy enum e1{e2, e3, e4, e5} e6(e5);
int main() {
e6 = e3;
auto e6 = e3 | e4;
cout << (e6 == e5) << endl;
}
6.5 强类型枚举
6.6 联合体
6.7 typedef
和 using
Copy using w = char[10];
w a[20];
7. 指针
7.0 一些说明
学习指针,至少从备考的角度来说,要更多的关注 怎么用 ,而非更多关注 是什么
说明几个概念,下文中都是表示如下意思:
地址:指的是 0x12356ff
这样的值,指针里面存的就是地址
学习一个指针(或者一个类似指针的东西,比如数组名),我们只要关心其以下几个特点:
7.1 内存和地址
7.2 指针和多级指针
Copy const char * s = "HelloWorld";
cout << (s + 3) << "\n";
int * p = (int*)s;
p++;
cout << (char*)p << "\n";
Copy int a = 2, b = 3;
int *p = &a;
int* q = &b;
int c, *r = &c;
多级指针
指针其实就是个用来存地址的变量,那么指针变量本身也有对应的内存地址
变量的地址可以被指针变量存储,即指针可以指向一个普通变量,那么也可以有一种变量用来存储指针变量的地址,即二级指针
Copy int a, b;
int* p = &a;
int* q = &b;
int** pp = &p;
**p = 233;
*PP = &b;
**p = 666;
7.3 数组
数组大小只能为正整数常量(包括const常量、C++11中constexpr
函数),实际使用时发现编译器也允许用变量定义数组大小,但这不好🥵
Copy int a[5] = {1, 2, 3, 4, 5};
int a[5]{1, 2, 3, 4, 5};
int a[5] = {1, 2, 3};
int a[] = {2, 3, 3, 3};
Copy int a[5];
a[5] = {1, 2, 3, 4, 5};
访问数组时注意下标为0~n-1
,下标越界语法上没有错误,会访问到未知的内存
Copy int a[3] = {0, 1, 2};
cout << a[-1];
Copy int a[3] = {1, 2, 3}, b[3];
b = a;
不要直接比较数组名来判断两个数组是否相等(实际上在比较地址)
Copy int a[3] = {1, 2, 3}, b[3] = {1, 2, 3};
if(b == a) {
}
以下方式对str的声明中,不能将str作为字符串使用的是:
Copy char str[9] = {'N', 'J', 'U', 'S', 'T'};
Copy char str[] = {"NJUST"};
Copy char str[] = "NJUST";
Copy char str[] = {'N', 'J', 'U', 'S', 'T'};
Copy int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
Copy int a[][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
Copy int a[][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
Copy int a[][3] = {{1, 3, 5}, {2, 4, 6}};
Copy int a[3][4] = {{1}, {5, 6}, {9, 10, 11}};
7.4 指针和数组
数组在很多时候,表现得像一个指针,但是它不是指针,不是指针
之所以这样,是很多时候我们使用数组的时候,它很容易就隐式转换成指针类型了
注:本人仅使用这张图代表的梗来和数组与指针类比,达到一种幽默诙谐的教学作用,并不代表本人认同或者不认同原图代表的观点和言论。
数组是一个常量,值不能被改变 ,值不能被改变 ,值不能被改变
Copy int a[3] = {1, 2, 3};
a++;
++a;
Copy char s[] = "Hello";
s = "world";
Copy int a[3] = {1, 2, 3};
int* p = a;
p++;
char s[] = "Hello";
char* q = s;
++q;
一级指针可以指向一个数组,就是指向数组首元素,把一个数组赋值给指针,是隐式转换为了常量指针赋值给指针
Copy int a[3] = {1, 2, 3};
int* p = a;
int* p = &a[0];
Copy int a[3] = {1, 2, 3};
int* p = a;
*(p + 2) = 114;
p[2] = 114;
*(a + 2) = 114;
a[2] = 514;
cout << 2[a];
当然了,还是有点差别,这俩优先级不一样,[]
优先级高于 *
Copy int a[5] = {1, 2, 3, 4, 5}, *p = a;
cout << *(p++);
cout << *p++;
cout << *++p;
cout << ++*p;
若有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
二维数组也同样表现得像一个二级指针常量,其指向不能被改变
Copy int a[2][3], b[2][3];
a++;
b = a;
a[0]++;
a[0] = b[0];
上面提到可以把数组视作指针常量来做题,但在以下情形中,二者有区别
Copy int a[3] = {1, 2, 3};
int (*p)[3] = &a;
7.5 指针数组和数组指针
7.5.0 指针数组
数组的元素是丰富多彩的,可以是基本类型。可以是结构体,可以是对象,那当然也可以是指针
指针数组 就是一个普通的数组,只不过它的每个元素都是指针类型
而数组本身又表现得像一个常量指针,故它可以看作和二级指针同级别
Copy int* p[3];
int a, b, c;
p[0] = &a;
p[1] = &b;
p[2] = &c;
7.5.1 数组指针
用于指向一个特定列宽的二维数组,其级别和二维数组相似
Copy int a[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
int (*p)[3] = a;
int (*p)[3] = &a[0];
Copy int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
int (*p)[4] = a;
*(*(p + 1) + 2) = 114;
p[1][2] = 114;
Copy int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
int (*p)[4] = a;
int *q[3] = {a[0], a[1], a[2]};
若有
Copy int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
int (*p)[4] = a;
则++*(*++p)
的值为多少?
7.6 二级指针总结
7.7 字符数组和字符串
C++基本类型中没有字符串,用字符数组 或字符指针 的形式来表示字符串(当然还有string)
Copy char s[10];
char str[4][20];
第一行是定义了一个字符数组,可以存字符串,最大长度为10(包括了最后的\0
)
第二行是定义了一个二维数组,也可以看做一个字符串的一维数组,可以存4个字符串,每个字符串最大长度是20(包括了最后的\0
)
Copy char s[5] = {'N', 'J', 'U', 'S', 'T'};
char s[10] = {'N', 'J', 'U', 'S', 'T'};
char s[2][10] = {{'N', 'J', 'U', 'S', 'T'}, {'C', 'h', 'i', 'n', 'a'}};
char s[10] = "NJUST";
char s[] = "NJUST";
char s[10] = {"NJUST"};
char s[][10] = {"NJUST", ""};
7.8 字符指针
字符指针本身只有一个指针变量的大小,可以指向一个常量字符串
Copy const char *str = "NJUST";
Copy char s[10] = "NJUST";
char *str = s;
7.9 字符数组和字符指针
Copy char s1[3][8] = {"I", "love", "NJUST"};
Copy char *s2[3] = {"I", "love", "NJUST"};
若有char *s1 = "C++";
和char s2[] = "C++";
,则下面选项不会导致错误的是:
7.10 字符串的输入输出
cin
或者cin.get
,利用循环,一个字符一个字符读入,其中cin
会跳过空格一类的字符,cin.get
可以读取空格、制表符、换行符;
cin>>str;
直接将一个个字符读入str
所在内存,末尾自动加\0
,cin
会被空格、制表符、换行符截断;
cin.getline(str, 10);
读入字符串,可读入空格、制表符,遇到回车结束;
cout << 字符指针或字符数组
,从给定位置一直输出字符直到遇到\0
;
7.11 字符串处理函数
字符串处理函数在头文件<cstring>
(<string.h>
)中;
strlen(str)
,返回int
类型的字符串长度(不包括\0),str
可以使字符数组,也可以是字符指针,也可以是字符串常量;
strcpy(s1, s2)
,返回值指向第一个参数所指位置的字符指针,从位置s1
开始,把s2
开始的字符串一个一个拷贝过来,s1
必须是指向可修改的变量内存的,注意数组越界问题
Copy char s1[] = "Peppa", s2[] = "Pig";
strcpy(s1, s2);
strcpy(s1 + 2, s2 + 1);
strcat(s1, s2)
,返回指向第一个参数所指位置的字符指针,将字符串s2
拼接到s1
后面,s1
必须是指向可修改的变量内存的,注意数组越界问题;
strcmp(s1, s2)
,返回一个int
整数,从前往后逐个位置比较s1
和s2
中对应位置的字符的ASCII码的值,若s1
大于s2
,则返回值大于0,若s1
小于s2
,则返回值小于0,相等则返回值为0;
VS中提供了strcpy_s
、strcat_s
的安全版本;
7.12 指针与函数
7.12.0 指针作为函数形参
Copy int f(int a[]);
int f(int a[10]);
int f(int *a);
下面哪一个函数能接受处理任意行、任意列的int矩阵,row表示行数,col表示列数:
Copy void print(int a[row][col]);
void print(int a[][col], int row);
void print(int *a[], int row, int col);
void print(int a[][], int row, int col);
若有
Copy int a[4] = {1, 2, 3, 4};
int b[3] = {1, 2, 3};
int c[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
int *d[] = {c[0], c[1], c[2]};
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 指针作为函数返回值
Copy int *f() {
int a = 3;
return &a;
}
Copy int *f(int *p) {
return p;
}
Copy int *f() {
static int a = 3;
return &a;
}
Copy int *f() {
int *p = new int(233);
return p;
}
7.12.2 函数指针
Copy int add(int a, int b) {
return a+b;
}
int main(void) {
int (*p)(int, int) = add;
int (*p)(int, int) = &add;
cout << p(1, 2) << endl;
cout << (*p)(1, 2);
return 0;
}
Copy typedef int (*FP)(int, int);
using FP = int (*)(int, int);
FP p = add;
设有语句
Copy int* f(int *p, int a) {return p + a;}
下面哪一个定义了返回值为int型指针的函数?
Copy int* (*fp1)(int*, int) = f;
Copy int* (*fp2[4])(int*, int) = {f};
Copy int* fun(int*(*fp)(int*, int)) {
return fp(nullptr, 3);
}
7.13 动态分配内存 new
和 delete
new
返回一个指向开辟出的内存的指针,若开辟了一个数组,则是指向首元素的指针
下面哪一条语句是正确的?
Copy float &y = new float;
Copy float &y = *new float;
Copy float &y = new float[3];
Copy float &y = new *float;
Copy int (*p)[4] = new int[3][4];
new
出来的内存在堆上,不在当前函数的栈内存上,不会自动回收
Copy int *p = new int(3);
delete p;
int *q = new int[5];
delete[] q;
7.14 const
指针、void
指针、空指针、野指针
7.14.0 const
指针
const
默认修饰左边,除非左边没有或者左边已经被修饰
Copy int const *p;
const int *p;
int * const p;
const int * const p;
int const * const p;
const int const * p;
int const const * p;
const指针的重载问题:之前在函数重载中提到,const不能作为重载的依据,即一个形参变量是不是const修饰,不作为形参的区别,但是指针这里有例外
Copy void f(int *p);
void f(const int *p);
void f(int * const p);
Copy void f(int &a);
void f(const int &a);
7.14.1 void
指针
通过强制类型转换,void指针可以控制许多变量,进行通用性设计
7.14.2 空指针和野指针
指针可以不指向任何地方,赋值为nullptr
即可(或NULL
)
野指针:被 delete
了的指针(这种也叫 悬垂指针)或者没有初始化赋值的局部变量指针,我们不知道它指向了哪里🤨
8. 引用
8.0 左值引用
可以把引用看作 功能阉割的指针,灵活性减小,但是使用更加方便了,但引用不是指针
const
修饰的左值引用可以引用字面值或者右值表达式
Copy const int &ri = 4;
const int &ri = a + 4;
8.1 引用与函数
Copy int &f1() {
static int count = 1;
return ++count;
}
int index;
int &f2() {
return index;
}
int main() {
f1() = 100;
for(int i=0; i<5; i++)
cout << f1() << " ";
f2() = 100;
int n = f2();
cout << n << endl;
f2() = 200;
cout << index << endl;
return 0;
}
返回动态开辟的内存的引用,注意用完之后delete
Copy int& f() {
int *p = new int(3);
return *p;
}
int main() {
int &rf = f();
cout << rf << endl;
delete rf;
return 0;
}
Copy int add(int a, int b) {
return a+b;
}
int main() {
int (&p)(int, int) = add;
cout << p(2, 3) << endl;
return 0;
}
8.2 引用与数组
可以有数组的引用,但数组的引用不能作为函数的返回值
Copy int a[] = {1, 2, 3};
int (&ra)[3] = a;
cout << ra[1];
8.3 引用与指针
Copy int a, &ra = a, *p = &ra;
Copy int a, *p = &a;
int* &rp = p;
8.4 &
符号
到目前为止,已经接触了 & 的三种用法,注意不要混淆
9. 链表
Copy struct Node {
int x;
Node* next;
};
处理链表的函数中,通常都是会判断是否是头结点,并做特殊处理,或者判断头结点是否为空;
通常需要两个指针来处理链表,一个用来指向新的结点,一个用来指向链表的某个位置;
遍历链表时,通常会出现p = p->next;
这样的语句;
1.已知一个单向链表结点的数据结构为:
Copy struct Node{
int data;
Node *next;
};
函数Node* AddNode (Node *head, int n)
的功能是:新建结点使之数据为n,将该结点处插入到head指向的单向链表中,放在数据与n差距最小的结点(如有多个最小差距结点,则考虑第一个)之后,并返回链头结点地址。若为空链,则新建结点直接作为头结点。
在程序空格位置填入合适的代码,完成AddNode
函数。
Copy Node * AddNode(Node *head,int n){
Node *p=new Node; //新建结点
/* (1) */ ;
p->next=NULL;
if(head==NULL){ //若为空链
/* (2) */ ;
}
Node *q; //指向待查结点
Node *mq=head; //指向差距最小的结点
for(q=head;q!=NULL;q=q->next)
if( /* (3) */ )
mq=q;
p->next= /* (4) */ ;
mq->next= /* (5) */ ;
return head;
}
2.在精准扶贫的大背景下,在政府和企业的帮助下,小明开了个养猪场。该养猪场有若干猪圈,我们用一个单向链表用于保存猪圈中猪的相关信息,其结点的数据结构为:
Copy struct Pig{
double weight;
Pig *next;
};
函数double Sell(Pig *&head, double threshold, double price)
的功能是:将体重超过threshold斤的猪出笼,以price元/斤的价格售出并返回总售价。具体操作为:将链表中weight超过threshold的结点全部删除,同时根据price元/斤的价格计算并返回总售价。
在程序空格位置填入合适的代码,完成Sell函数。
Copy double Sell(Pig *&head, double threshold, double price){
double total=0;
Pig *p1,*p2=head;
while( /* (1) */ ){ //判断头结点是否要删除
/* (2) */ ; //下面删除头结点
total+=p2->weight;
delete p2;
p2=head;
}
if(head) { //遍历链表,判断是否有非头结点要删除
for(p1=p2=head;p2;p1=p2,p2=p2->next)
if(p2->weight>threshold){ //删除非头结点的结点
/* (3) */ ;
total+=p2->weight;
delete p2;
/* (4) */ ;
}
}
/* (5) */ ;
}