跳转至

C 基础

注释

/**
 * 多行注释
**/

;  // 行内注释

变量

变量类型的识别按顺时针法则

基本数据类型

  • [signed, unsigned] char
  • [signed, unsigned] short, int, long, long long
  • float, double

类型转换

自动类型转换

自动类型转换的规则如下:

  • 在表达式里,charshort类型自动转换为int,需要时转换为unsigned int
  • 在传递给printf("%f")时,float自动转换为double。(在printf中,floatdouble的格式说明符都是%f。)
  • 在包含两种数据类型的运算中,两个值被转换成两种类型里较高级的类型。
  • 在赋值语句中,计算的最后结果被转换成被赋值的变量的类型(可能导致降级)。

复合数据类型

数组

声明和初始化:

type identifer[N] = {elem1, elem2, ..., elemN};

N可被省略,编译器将根据指定元素的个数初始化数组;部分初始化数组,数组其余元素会被自动初始化为0。

使用{[1]=elem1, ..., elemN}语法来指定初始化。如果指定初始化元素后有更多元素,则它们被用于后续数组元素的初始化。如果多次对一个元素初始化,最后一次有效。

使用花括号对数组初始化的语法仅在数组声明时有效。不能将数组作为一个整体进行赋值。

多维数组

type identifier[X][Y];

identifier 是一个由x个元素组成的数组,而它的每一个元素是y个type类型元素组成的数组。

可以使用花括号语法声明和初始化数组:

type identifier[X][Y] = {
    {elem11, elem12, elem13, ..., elem1y},
    {elem21, elem22, elem23, ..., elem2y},
    ...,
    {elemx1, elemx2, elemx3, ..., elemxy}
}

使用花括号声明时也可以去掉内部的花括号,前面的元素优先得到赋值。

使用指针操作多维数组,复杂度随数组维数的增加而增加。地址的地址和指针的指针是双重间接(double indirection)的一个典型例子。

区分指针的数组和数组的指针。int * ptr[2]是有两个int类型指针的数组而int (* ptr) [2]是指向一个拥有两个int类型数据的数组的指针。方括号的结合性高于*(可以借助“靠近标识符的先结合”来判断类型)。

ptr[2] 是一个含有两个元素的数组 → * ptr[2] 是一个含有两个元素的数组,数组的元素是指针 → int * ptr[2] 是一个含有两个元素的数组,数组的元素是指向int类型数据的指针;

(* ptr) 是一个指针 → (* ptr) [2] 是一个指针,指向拥有两个元素的数组 → int (* ptr) [2] 是一个指针,指向拥有两个int类型元素的数组。

声明数组参数

在声明形参时,可以使用int ar[]代替int * ar。使用这样的形式可以提示 ar 是指向int数组中一个元素的指针。无论怎样声明,处理数组的函数实际上是使用指针作为形式参数的。

如果不希望改变数组内容,可以用*const*修饰形参中的指向数组的指针。

声明处理多维数组的函数的形式参数,可以使用int (* ptr) [n][x][y]和它的等价形式int ptr[][x][y](第一个方括号留空表示它是指针,如果第一个方括号没有留空,效果上等价于留空的)。

变长数组(VLA)

变长数组必须是自动储存类的(一例为在函数中),不能在初始化时进行声明。

声明处理变长数组的函数,变长数组参量需要在数组之前声明。如int sum2d(int rows, int cols, int ar[rows][cols]);,可以省略一般形参的名称和变长数组的维数,如int sum2d(int, int, int ar[*][*]);

复合文字

复合文字(Compound literal)是表示数组和结构内容的字面值。它没有标识符。

(int [2]){1, 2}

上面的表达式创建了一个含有2个int元素的数组。表达式的值是指向数组首元素的指针。

可以省略元素的个数:

(int []){1, 2, 3}

也适用于多维数组,但只能省略第一个维度。

(int [][4]) {
    {1, 2, 3, 4},
    {5, 6, 7, 8}
}

可以使用复合文字初始化一个指针,或者使用复合文字作为实际参数传递给参量形式匹配的函数。

指针

注意分辨声明形式:

int* p1, p2; // 可能不是想要的结果。
int *p1, *p2;
  • 地址运算符 &
  • 间接运算符(地址访问运算符) *

ptr = &bah; val = * ptr; 等价于 val = bah;

使用 type * identifier; 来声明指针。

不要对未初始化的指针取值。

自增运算符与间接运算符存在微妙的优先级关系,试区分*ptr++*++ptr*(ptr++)*(++ptr)(*ptr)++。其中*ptr++等价于*(ptr++)

使指针自增1,指针指向下一个储存单元(而不是下一个字节)。

如果指针被*const*修饰,那么指针指向的数据不应该被改变。不能把*const*指针赋给非*const*指针(这样指针指向的数据可能会改变),但可以把非*const*指针赋给*const*指针,前提是只进行一层间接运算(即被赋值的*const*指针不能是指针的指针)。

下面的例子演示了如果忽略上述前提,在双重间接的情况下*const*内容被修改的例子:

const int ** pp;
int * p;
const int n = 10;

pp = &p;    // 不允许
*pp = &n;   // 使p指向n
*p = 11;    // n被修改

指针运算

  • 赋值(Assignment) 将地址赋给指针,可以使用地址运算符或数组名。还可以使用类型指派来为不兼容的指针赋值。
  • 求值(value-finding)或取值(derefering) 使用间接运算符。
  • 取指针地址 使用地址运算符。
  • 指针的整数加法和减法
  • 求差值(differencing) 差值的单位是相应类型的大小,而不是字节。有效差值运算的前提是两个指针指向同一个数组
  • 比较 可以使用关系运算符比较两个相同类型的指针。

一个指针不能另一个指针相加或相乘。

指针与数组的关系

在表达式中使用数组名,等同于使用该数组首元素的地址,即数组名是指向首元素的(只读)指针。

指针变量和数组标识符都可以使用方括号语法和指针语法:ar[n]等价于*(ar + n)。区别在于,指针可以通过自增自减运算符(如ar++)来修改指向的地址,而数组名虽是指针,却是指向数组首元素的只读指针,不能被修改,即ar++是无效的。