2021-09-28
1
有符号整数的二进制最高位为符号位,$0$ 表示正数,$1$ 表示负数。
2
反码指将二进制原码的数值位逐位取反。
正整数的补码就是它的原码,负整数的补码是它的反码加 $1$。
3
一个正整数减去一个正整数可以看作一个正整数加上一个负整数:
$A_原 - B_原 = A_原 + (-B)_补 - 2^m$
2021-10-08
1
$float$ 类型变量在计算机中用 $4$ 个字节($32$ 位)存储:
SEEE EEEE EMMM MMMM MMMM MMMM MMMM MMMM
将十进制数改写成 $n \times 2^m$($1 \leq n < 2$)。
S:符号位,$0$ 为正数,$1$ 为负数。
E:指数位,$m$ 加上 $127$ 后的二进制数($m$ 的范围为 $-127 \sim 128$)。
M:底数位,$n$ 减去 $1$ 后,不断乘以 $2$ 并进行如下操作;
若结果大于等于 $1$,则从左至右填入一个 $1$,并将计算值减 $1$;若结果小于 $1$,则从左至右填入一个 $0$。
2
转义字符
\n :回车换行
\t :横向跳到下一制表位置
\b :退格
\r :回车
\f :走纸换页
\0 :空字符 (=NULL)
\ddd :$1 \sim 3$ 位八进制数所代表的字符
\xhh :$1 \sim 2$ 位十六进制数所代表的字符
3
格式字符
%u :十进制无符号整数
%o :八进制无符号整数
%x/%X :十六进制无符号整数($a \sim f$ 以小写/大写形式输出)
%e/%E :指数形式实数(指数 $e$ 以小写/大写形式输出)
%p :十六进制地址
%* :读入但不赋给变量
2021-10-29
1
变量的存储类型包括 $auto$(自动型)、$register$(寄存器型)、$extern$(外部参照型)、$static$(静态型)。
2
自动型变量 $auto$
在函数内或复合语句中定义自动型变量时 $auto$ 可缺省。
自动型变量只能作内部变量,所以只在定义它的函数或复合语句内有效,即“局部可见”。
不同函数或复合语句中声明的具有相同名字的各个局部变量之间没有任何关系。
#include <stdio.h> int main() { int x = 5; printf("x = %d\t", x); if (x > 0) { int x = 10; printf("x = %d\t", x); } printf("x = %d\n", x + 2); return 0; } // x = 5 x = 10 x = 7
第二个 printf 输出时,虽然 if 外定义的 $x$ 变量的内存还没释放,但规定以内层说明优先,即相当于内层说明的变量 $x$ 是另外一个变量 $x'$,在其所在的花括号内如果不包括更深层次的同名变量说明,则其中所引用 $x$ 就是 $x'$。
下面是定义在函数中的例子。
#include <stdio.h>
void func() { int a = 0; printf("a of func() is %d\n", ++a); } int main() { int a = 10; func(); printf("a of main() is %d\n", ++a); func(); printf("a of main() is %d\n", ++a); return 0; } // a of func() is 1 // a of main() is 11 // a of func() is 1 // a of main() is 12
3
寄存器型变量 $register$
寄存器型变量储存在 CPU 的通用寄存器中,因为数据在寄存器中操作比在内存中快得多,因此通常把程序中使用频率最高的少数几个变量定义为 $register$ 型,目的是提高运行速度。
分配寄存器的条件:有空闲的寄存器;变量所表示的数据的长度不超过寄存器的位长。
寄存器型变量的作用域和寿命与自动型变量相同。
4
外部参照型变量 $extern$
外部参照型变量一般用于在程序的多个编译单位之间传送数据,其存储空间在静态数据区,在程序执行过程中长期占用空间(全局存在,全局可见)。
/*file1.c*/ #include <stdio.h> int mul() { extern int a, b; return a * b; } int a, b, c; int main() { scanf("%d%d%d", &a, &b, &c); printf("%d * %d = %d", a, b, mul()); printf("%d ** %d = %d", a, c, pow()); return 0; } /*file2.c*/ extern int a, c; int pow() { int d = 1, i; for (i = 0; i <= c; i++) d *= a; return d; } // 2 3 4 // 2 * 3 = 6 // 2 ** 4 = 16
如果外部变量不在文件的开头部分定义,其有效的作用范围只限于定义处到文件结束。如果定义点之前的函数想引用外部变量,则应该在引用前用 $extern$ 对变量进行声明(见 file1.c)。
file2.c 中的 $extern$ 告诉程序 $x$ 是外部参照变量,应在本文件之外去寻找它的定义,本文件不必再次为它分配内存。
5
静态型变量 $static$
静态型变量既可以在函数或复合语句内定义,也可以在所有函数之外定义。前者称为局部静态变量,后者称为全局静态变量。
静态型变量都是全局存在的,全局静态变量全局可见,局部静态变量局部可见。
局部静态变量在函数调用结束后不消失而保留原值,即其占用的存储单元不释放,在下次该函数调用时,该变量已有值,其值就是上一次函数调用结束时的值。也就是对静态局部变量只初始化一次。
#include <stdio.h> void func() { int a = 0; static int b = 0; printf("a = %d, b = %d\n", ++a, ++b); } int main() { for (int i = 1; i <= 3; i++) func(); return 0; } // a = 1, b = 1 // a = 1, b = 2 // a = 1, b = 3
有时在程序设计中希望某些外部变量只限于被本文件引用,而不能被其他文件引用,可以在定义外部变量时加一个 $static$ 声明。
/*file1.c*/ extern int x; void main() { x++; } /*file2.c*/ extern int x; void fun1() { x += 3; } /*file3.c*/ static int x = 0; void fun2() { printf("%d", x); }
虽然在 file1.c 和 file2.c 中用了 $extern$,但都无法使用 file3.c 中的全局变量 $x$。
2021-11-26
1
寄存器变量没有存储地址,所以寄存器变量不能做单目 & 的操作对象。
2
指针可以和零之间进行等于或不等于的关系运算($p==0$ 或 $p \; !=0$),用于判断指针 $p$ 是否为一个空指针。
2021-12-03
1
定义一个数组指针(p 的类型为 int *[4],表示指向一个长度为 $4$ 的一维数组元素的指针):
int (*p)[4];
不能把一位数组的首地址直接赋给指向相同数据类型的数组指针,而必须使用强制类型转换。
int a[4], (*ap)[4]; ap = a; // 错误 ap = (int (*)[4])a; // 正确
下面是对应的输入与输出。
for (i = 0; i < 4; i++) scanf("%d", *ap + i); for (i = 0; i < 4; i++) printf("%d ", *(*ap + i));
显然,数组指针访问一维数组的元素比起普通指针变量要麻烦一些,我们通常用它来处理二维数组。
int a[3][4], (*ap)[4], i, j; ap = a; for (i = 0; i < 3; i++) for (j = 0; j < 4; j++) scanf("%d", *(ap + i) + j); for (i = 0; i < 3; i++) for (j = 0; j < 4; j++) { printf("%d ", *(*(ap + i) + j)); printf("%d ", ap[i][j]); // 同上 }
当然也可以用指针数组进行相似的处理。
int a[3][4], *ap[4], i, j; for (i = 0; i < 3; i++) ap[i] = a[i]; for (i = 0; i < 3; i++) for (j = 0; j < 4; j++) scanf("%d", *(ap + i) + j); for (i = 0; i < 3; i++) for (j = 0; j < 4; j++) { printf("%d ", *(*(ap + i) + j)); printf("%d ", ap[i][j]); // 同上 }
此外,不能将二维数组的首地址赋给二级指针。(相关解释)
2
一维数组作函数参数:
void func(int a[], int n) void func(int *a, int n)
二维数组作函数参数:
void func(int a[][4], int n) void func(int (*a)[4], int n)
3
不能用 $auto$ 型变量的地址初始化内部的 $static$ 型指针。
2021-12-10
1
定义一个函数指针($add$ 函数省略):
int (*p)(int, int) = add;
调用函数指针有两种格式:
int a = 1, b = 2, c; int (*p)(int, int) = add; c = (*p)(a, b); c = p(a, b); // 同上
虽然上面两个调用语句是等效的,但两者实际上却有不同的操作含义。前者是对函数指针进行去内容运算,使得程序控制转移到函数的入口地址;后者是直接用函数指针名取代函数名,因为函数名是一个地址常量,把它赋给了函数指针变量后,函数指针的内容就是函数名,当然可以用函数指针名直接取代函数名。
2021-12-17
1
两种非常规的字符串初始化方式:
int *s = "abc"; int s[] = "abc";
两者的区别在于,前者不可以修改,后者可以修改。因为前者用指针指向存储在静态存储区的字符串常量,而后者是将字符串存入字符数组。
2
$gets$ 函数输入字符串以回车结束,$puts$ 函数输出字符串末尾自动回车换行。
3
使用结构指针对结构成员进行引用:
struct student *a; int a_id = (*a).id; int a_id = a->id;
4
结构体变量可以直接用等号 = 赋值,但结构体含有指针成员时一般不这样使用。(相关解释)
2021-12-31
1
对表达式求值时,不能简单地对优先级高的运算符进行运算,而是先对表达式进行语法分析得到一棵语法树(二叉树),然后后序遍历语法树求值。
运算符的优先级反映了运算符在语法树上的位置,优先级越低的运算符在语法树中越高的位置。按照后序遍历的顺序,我们总是先对子节点求值,所以在位置越低的运算符越先运算。
括号的优先级最高,所以一对括号内的内容一定构成一棵完整的语法树,且树的根节点下方不存在括号外的内容。