Press "Enter" to skip to content

基础知识 – C++ 程序设计主要知识点

1. 表达式

定义:由常量、变量、函数调用及运算符构成的算式

// 假设定义了 int x, y, *p, printf() 等变量及函数。以下均为表达式:
3.5
"ABC"
x // 左
2 + 3 * x
x > 3
x = 3 // 左
x = *p + y + printf("ABC") // 左
"abc"[1]
sizeof(int)
sizeof 3
x = y = 3 // 左
(y = x) = 3 // 左
(++x = 3)++ // 左

// 注意:运算有优先级与结合性,结果有左值与右值

2. 左值、右值表达式

左值:即能出现在等号左边的值

右值:只能出现在等号右边的值

左值一定是右值,反之不一定成立

3. 只读、挥发和引用型存储

一、const 变量、参数或返回值

定义:只读变量(不变变量)是使用const定义,定义时须立即初始化

const double pi = 3.14
// 常用来替代
#define pi 3.14
// x, y 分别是左值变量和右值变量
int x;
const int y = 3;

const int *const r = &x;
const int *p = &x;
// 不可 *p = 8 ,但可 x = 8

int *q = &y;
// 错误
// 反证法:若允许 q = &y ,因 *q = 8 正确,则会改变 y 。故 q = &y 不被允许。

二、volatile 变量、参数或返回值

定义:挥发变量是使用 volatile 定义的,程序自身不改变其值,但其值会发生变化(多任务:另外一个任务在改变)。

volatile int x = 3;

// x == 4 是有意义的,x 是左值且随时发生变化
if (x == 4) {
  x = 7;
}
// 定义
const volatile int y = 7; // 正确吗?
// 正确
// 关于 const :本程序说明 y 有初值 7 ,且不能修改 y 的值(故 y 是右值)
// volatile 说明: y 的值随时变化,隐含地说明其他程序可修改 y 值

三、void 参数或返回值

意义:表示函数无参无返回值。其真正意义是无确定类型

// f 无参,无返回值
// g 有int类型的参数,无返回值
void f(void), g(int);
// 定义
int x = 7; double y = 8.0;

void *p = &x; p = &y; // 正确吗?
// 正确;
// p 指向的单元类型不确定,p 管它指向什么类型的变量

*p = 7; *p = 8; // 正确吗?
// 错误;
// p 指向的单元类型不确定:赋值过去占用几个字节不确定

// 改写
*(int *)p = 7; *(double *)p = 8.0; // 强制类型转换:编码者负责

delete p; delete new int; delete new double; // 正确吗?
// 正确;
// 因 delete 的参数类型为 void * 
// 可以使用任何非成员指针赋值给 void * 类型的变量或参数

四、引用性变量、参数或返回值

定义:引用变量使用 & 定义,它共享被引用变量的内存

推论:理论上它自己不分配内存,被引用的变量必须分配内存

注意:左值引用变量必须在定义的同时用相同类型左值表达式初始化

// x = 3 为 int 型左值,可初始化 int 型左值引用 y
int x;
int &y = x = 3;
const int &m = 3.0;
const int &z = x;

y = 8; // 正确吗?
// 正确;
// 右值引用变量可用类型相容的右值表达式初始化
// 常量 3.0 可转化为右值(即 const int 类型的值),x 既是左值又是右值
// y = 8 可使 x = z = 8
// 定义
int x; int &y = x;
int f() {
  return 3;
}
int &g(int &m) {
  return (m > 1) ? x : m;
}

// 那么
f() = 2;
g(x) = g(y) = 6; // 正确吗?

// int f() 可看做 const int f() ,故 f() = 2 错误
// g(y) = 6 正确,且使 x = 6
// int &g(int &m) 返回左值,故调用时可被赋值,不可看做 const int &g(int &m) 返回右值
// 注意:不能说 m 引用 y ;被引用对象 y 必须分配内存,而 y 为引用故理论上不分配
// 只能说 m 和 y 共同引用 x
int &&n;
int &*p;
int *const &q = &x; // 正确吗?

// n, p 各引用或指向不分配内存的引用,故错
// q 引用的是常(右值)指针(指针分配有内存):可用 &x 初始化
// 去掉 const 则需左值 int *h; int *&r = h;

4. 类型解析

方法:解析或分析类型表达式时,优先级高的运算符先解析,相同时则按结合性方向进行

例如:

  • 左 -> 右:()[][][]()()
  • 右 -> 左:*&**

等价性

int *f();
// f 右边的运算符是 () ,高于左边的运算符 * 。故先解析 f 是一个函数,函数返回指针指向 int 类型

int(*g)();
// (*g)() 两个 () 的优先级相同,结合方向 左 -> 右 。故先解析(*g) ,说明 g 是一个指针变量,而后解析右边 () ;g 指向无参数函数返回 int 值。

// 证明
int main(int argc, char **argv);
// 等价与
int main(int argc, char *argv[]);

// 证明两者等价,即证明 char **argv 等价于 char *argv[]
// 注意:指针可以看做数组,数组也可以看作指针

// 证明:
//// char **argv 等价于 char *(*argv)
//// 结合性:右 -> 左,先解析右边的 *
//// char *(argv[])
//// 指针看作数组:"ABC"[1] == "B"
//// char *argv[]
//// () 和 [] 的优先级相同,均高于 * ,去掉 () 不影响

new 和 delete

运算符 new 的右边是类型表达式,若为数组仅第一维是动态的

int (*p)[20][30] = new int [x + 3][20][30];

A *q = new A; // 先分配 A 大小的内存,然后调用构造函数 A()
// 可证:p 的类型 int (*)[20][30] 等价于 new 右边 int[][20][30]

运算符 delete 的右边是指针类型或指针数组类型

delete []p;
delete q; // 先调用析构函数,然后释放 A 大小的内存

5. 变量、函数的说明与定义

变量

变量定义:描述变量的类型名称初始值。 一个程序只能定义一次变量或函数。

变量说明:描述变量的类型名称。说明可重复多次

extern int x = 3;
int y = 2;
int z; // z有默认值,当它是全局变量时为0,当它是局部变量时为随机的
extern int x;
extern int x, y; // 说明 x 可以有多次

函数

一、函数定义

描述函数的返回类型名称参数类型函数体

二、函数说明

描述函数的返回类型名称参数类型。可多次说明。

问题: 为何 stdio.h 文件中都是说明? 为何 C++ 提倡 .h 文件里都是说明?

答案: 程序两模块 A.CPPB.CPP 均用 stdio.h ,若 scanf 是定义,程序就定义 scanf 两次。

三、函数参数

默认参数:即有默认值的参数。

  1. 所有默认参数必须在参数表右部,默认参数中间不能有非默认参数
  2. 默认值中不得出现同一参数表的参数名;错误:int g(int w, int x = 2, int y = w + 2) ,默认值 w + 2 出现 w
int m, f(int w, int x = 1, int = 2, int z = m + 3)

省略参数:用 ... 定义,表示任意个任意类型的参数

// 参数顺序存放,由 s 获取省略参数(下一单元)
int printf(const char *s, ...);

// 参数顺序存放,由 n 获取省略参数(下一单元)
int sum(int n, ...) {
  // p 指向 n 的下一单元,即第一个省略参数
  int s = 0, k, *p = &n + 1;
  // n 个省略参数求和
  for (k = 0; k < n; k++) {
    s += p[k];
  }
  return s; 
}


void main() {
  int w = sum(3, 1, 2, 4); // w = 1 + 2 + 4 = 7 ,共 3 个数相加
  w = sum(4, 2, 3, 6, 7); // w = 2 + 3 + 6 + 7 = 18 ,共 4 个数相加
}

四、函数重载

返回类型相同的同名函数,通过参数不同完成不同功能

// 参数个数不同,调用时参数类型要完全匹配
int f(int);
int f(int, int);

// 参数类型有所不同,同上匹配参数
int g(int, double);
int g(int, int);

五、函数内联

inline 定义的函数,在类体里定义函数体则自动内联

注意:

  1. 仅在其当前作用域内可访问;用函数体代替调用,减少开销提高效率。
  2. 取函数地址、先调用后定义函数体、包含分支类语句则内联失败:失败不表示语法错误。分支类语句: ifswitchwhiledo-whilefor? : 以及函数调用。
// 在 A.CPP 中定义内联函数 f() ,f 仅在当前作用域内即文件 A.CPP 内可访问

inline int f() {
  return 1;
}

void main() {
  int x, y;
  x = f(); // 编译后的汇编程序:直接将 1 赋值给 x , 不调用函数 f
  y = f(); // 编译后的汇编程序:直接将 1 赋值给 y , 不调用函数 f
}

// 文件 B.CPP 将无法访问 f
// 类似的,类体里定义函数体(如友元)函数仅在当前类访问

    发表回复

    您的电子邮箱地址不会被公开。 必填项已用 * 标注