c语言值得注意的知识
1.说明下列每对scanf格式串是否等价?如果不等价,请指出它们的差异。
(c) "%f"与"%f "。
在 `scanf` 函数中,`"%f"` 和 `"%f "` 这两种格式的区别在于后面的空格。
1. `scanf("%f", &variable);` 这种情况下,`scanf` 会读取并解析用户输入的浮点数,然后将解析的值存入 `variable` 中。一旦读取到非数字字符(例如空格,换行,制表符等),`scanf` 就会停止读取。
2. `scanf("%f ", &variable);` 这种情况下,`scanf` 在解析并存储浮点数之后,还会继续读取并忽略掉后面的任何空白字符(包括空格,换行和制表符)。也就是说,`scanf` 将会读取直到下一个非空白字符为止。
简单地说,`"%f"` 读取一个浮点数,然后停在后面的第一个非数字字符上,而 `"%f "` 读取一个浮点数,并且会跳过其后的所有空白字符,直到遇到下一个非空白字符。
假设我们有下面这样的输入:`12.34 56.78`
如果你的代码如下:
float a, b; scanf("%f", &a); scanf("%f", &b);
`scanf("%f", &a);` 会读取 `12.34`,然后停止在空格处,因为空格是非数字字符。然后,`scanf("%f", &b);` 会从空格后的 `56.78` 开始读取,因此,`a` 将得到 `12.34`,`b` 将得到 `56.78`。
然而,如果你的代码是这样的:
float a, b; scanf("%f ", &a); scanf("%f", &b);
`scanf("%f ", &a);` 会读取 `12.34`,然后继续读取并忽略后面的空格,停在 `56.78` 的前面。然后,`scanf("%f", &b);` 将从 `56.78` 开始读取。所以,结果仍然是 `a` 得到 `12.34`,`b` 得到 `56.78`。
区别在于,如果你在 `"%f "` 后面还想继续读取空白字符或者做其他的 `scanf` 操作,可能就会出现预料之外的结果,因为 `"%f "` 会跳过浮点数后面的所有空白字符。
2. scanf 输入陷阱
假设scanf函数调用的格式如下:scanf("%d%f%d", &i, &x, &j); 如果用户输入10.3 5 6 调用执行后,变量i、x和j的值分别是多少?(假设变量i和变量j都是int型,变量x是float型。)
scanf("%f%d%f", &x, &i, &y);
12.3 45.6 789
x = 12.3
i = 45
y = 0.6
3. 关于 %
运算符%要求操作数是整数。如果两个操作数中有一个不是整数,程序将无法编译通过。
问:我想把%运算符用于浮点数,但程序无法通过编译,该怎么办?(p.41)
答:%运算符要求操作数是整数,这种情况下可以试试fmod函数
4. v+=e不等价于v = v + e。
*问:前面提到:如果v有副作用,那么v+=e不等价于v = v + e。可以解释一下吗?(p.46)
答:计算v += e只会求一次v的值,而计算v=v +e会求两次v的值。在后一种情况下,对v求值可能引起的任何副作用也都会出现两次。
在下面的例子中,i只自增一次:
a[i++] += 2;
如果用=代替+=,语句变成
a[i++] = a[i++] + 2;
i的值在别处被修改和使用了,因此上述语句的结果是未定义的。i的值可能会自增两次,但我们无法确定到底会发生什么。
5. If i
and j
are positive integers, does (-i)/j
always have the same value as -(i/j)
? Justify your answer.
No. The C89 and C99 standards implement division of negative numbers differently: (-9)/7
can produce -1 or -2 in C89,
while -(9/7)
will always produce -1. C99 will always truncate the remainder towards zero, however, so the answers produced by (-i)/j
and -(i/j)
will be equivalent.
例题:
What is the value of each of the following expressions in C89? (Give all possible values if an expression may have more than one value.)
(a) 8 / 5
(b) -8 / 5
(c) 8 / -5
(d) -8 / -5
Solution
(a) 1
(b) -1 or -2
(c) -1 or -2
(d) 1 or 2
例题:
What is the value of each of the following expressions in C89? (Give all possible values if an expression may have more than one value.)
(a) 8 % 5
(b) -8 % 5
(c) 8 % -5
(d) -8 % -5
Solution
(a) 3
(b) -3 or 5
(c) 3 or -5
(d) 3 or 5
6.数字的存储
7.不同进制常量的注意事项
十进制常量包含0~9中的数字,但是一定不能以零开头:
15 255 32767
八进制常量只包含0~7中的数字,而且必须要以零开头:
017 0377 077777
十六进制常量包含0~9中的数字和a~f中的字母,而且总是以0x开头:
0xf 0xff 0x7fff
十六进制常量中的字母既可以是大写字母也可以是小写字母:
0xff 0xfF 0xFf 0xFF 0Xff 0XfF 0XFf 0XFF
请记住八进制和十六进制只是书写数的方式,它们不会对数的实际存储方式产生影响。(整数都是以二进制形式存储的,跟表示方式无关。)任何时候都可以从一种书写方式切换到另一种书写方式,甚至可以混合使用:10 + 015 + 0x20的值为55(十进制)
十进制整型常量的类型通常为int,但如果常量的值大得无法存储在int型中,那就用long int类型。如果出现long int不够用的罕见情况,编译器会用unsigned long int做最后的尝试。确定八进制和十六进制常量的规则略有不同:编译器会依次尝试int、unsigned int、long int和unsigned long int类型,直至找到能表示该常量的类型。
要强制编译器把常量作为长整数来处理,只需在后边加上一个字母L(或l):
15L 0377L 0x7fffL
要指明是无符号常量,可以在常量后边加上字母U(或u):
15U 0377U 0x7fffU
L和U可以结合使用,以表明常量既是长整型又是无符号的:
0xffffffffUL。(字母L、U的顺序和大小写无所谓。)
8. 整数溢出
有符号整数运算中发生溢出时,程序的行为是未定义的。
无符号整数运算过程中发生溢出时,结果是有定义的:正确答案对2n取模,其中n是用于存储结果的位数。例如,如果对无符号的16位数65 535加1,其结果可以保证为0。
9.读/写整数
10. 浮点常量
浮点常量可以有许多种书写方式。例如,下面这些常量全都是表示数57.0的有效方式:
57.0 57. 57.0e0 57E0 5.7e1 5.7e+1 .57e2 570.e-1
浮点常量必须包含小数点或指数;其中,指数指明了对前面的数进行缩放所需的10的幂次。
如果有指数,则需要在指数数值前放置字母E(或e)。可选符号+或-可以出现在字母E(或e)的后边。
默认情况下,浮点常量以双精度数的形式存储。
换句话说,当C语言编译器在程序中发现常量57.0时,它会安排数据以double类型变量的格式存储在内存中。这条规则通常不会引发任何问题,因为在需要时double类型的值可以自动转换为float类型值。
在某些极个别的情况下,可能会需要强制编译器以float或longdouble格式存储浮点常量。为了表明只需要单精度,可以在常量的末尾处加上字母F或f(如57.0F);而为了说明常量必须以longdouble格式存储,可以在常量的末尾处加上字母L或l(如57.0L)
11. 读写浮点数
读取double类型的值时,在e、f或g前放置字母l:
double d;
scanf("%lf", &d);
注意:只能在scanf函数格式串中使用l,不能在printf函数格式串中使用。在printf函数格式串中,转换e、f和g可以用来写float类型或double类型的值。(C99允许printf函数调用中使用%le、%lf和%lg,不过字母l不起作用。)
读写long double类型的值时,在e、f或g前放置字母L:
long double ld;
scanf("%Lf", &ld);
printf("%Lf", ld);
12. 字符常量
字符常量事实上是int类型而不是char类型
13. 用scanf和printf读/写字符
转换说明%c允许scanf函数和printf函数对单个字符进行读/写操作:
char ch; scanf("%c", &ch); /* reads a single character */ printf("%c", ch); /* writes a single character */
在读入字符前,scanf函数不会跳过空白字符。
如果下一个未读字符是空格,那么在前面的例子中,scanf函数返回后变量ch将包含一个空格。
为了强制scanf函数在读入字符前跳过空白字符,需要在格式串中的转换说明%c前面加上一个空格:
scanf(" %c", &ch); /* skips white space, then reads ch */
回顾3.2节的内容,scanf格式串中的空白意味着“跳过零个或多个空白字符”。
因为通常情况下scanf函数不会跳过空白,所以它很容易检查到输入行的结尾:检查刚读入的字符是否为换行符。
例如,下面的循环将读入并且忽略掉当前输入行中剩下的所有字符:
do { scanf("%c", &ch); } while (ch != '\n');
下次调用scanf函数时,将读入下一输入行中的第一个字符。
14. getchar 与 scanf 混用时需要注意些什么
如果在同一个程序中混合使用getchar函数和scanf函数,请一定要注意。
scanf函数倾向于遗留下它“扫视”过但未读取的字符(包括换行符)。
思考一下,如果试图先读入数再读入字符的话,下面的程序片段会发生什么:
printf("Enter an integer: "); scanf("%d", &i); printf("Enter a command: "); command = getchar();
在读入i的同时,scanf函数调用将留下没有消耗掉的任意字符,包括(但不限于)换行符。
getchar函数随后将取回第一个剩余字符,但这不是我们所希望的结果。
惯用方法:
while (getchar() != '\n') /* skips rest of line */ while ((ch = getchar()) == ' ') /* skips blanks */
15. 不同类型的变量进行比较或者算术运算时应该注意些什么:
执行常规算术转换的规则可以划分成两种情况。
1.任一操作数的类型是浮点类型的情况

16. 类型转换
C语言把(类型名)视为一元运算符。一元运算符的优先级高于二元运算符,因此编译器会把表达式
(float) dividend / divisor 解释为 ((float) dividend) / divisor
如果感觉有点困惑,还有其他方法可以实现同样的效果:quotient = dividend / (float) divisor;
long i; int j = 1000; i = j * j; /* overflow may occur */
printf("Size of int: %lu\n", (unsigned long) sizeof(int));
18. 十六进制的浮点常量是什么样子?使用这种浮点常量有什么好处?
答:十六进制浮点常量以0x或0X开头,且必须包含指数(指数跟在字母P或p后面)。
指数可以有符号,常量可以以f、F、l或L结尾。指数以十进制数表示,但代表的是2的幂而不是10的幂。
例如,0x1.Bp3表示1.6875×2^3 = 13.5。
十六进制位B对应的位模式为1011;因为B出现在小数点的右边,所以其每一位代表一个2的负整数幂,把它们(2^ -1 + 2^ -3 + 2^ -4)相加得到0.6875。
十六进制浮点常量主要用于指定精度要求较高的浮点常量(包括e和π等数学常量)。十进制数具有精确的二进制表示,而十进制常量在转换为二进制时则可能受到舍入误差的些许影响。十六进制数对于定义极值(例如<float.h>头中宏的值)常量也是很有用的,这些常量很容易用十六进制表示,但难以用十进制表示
19. 类型定义和宏定义
20.
Does the following statement always compute the fractional part of f
correctly (assuming that f
and frac_part
are float
variables)?
frac_part = f - (int) f;
If not, what's the problem?
No, the statement could fail if the value of f is larger than the largest value of int.
21. 指示器
22. 确定数组长度
#define SIZE ((int) (sizeof(a) / sizeof(a[0])))
23. 实际参数的转换
24. 传递数组的函数原型
多维数组:
int sum_two_dimensional_array(int n, int m, int a[n][m]); int sum_two_dimensional_array(int n, int m, int a[*][*]); int sum_two_dimensional_array(int n, int m, int a[][m]); int sum_two_dimensional_array(int n, int m, int a[][*]);
一维数组:
int sum_array(int n, int a[n]); /* Version 1 */ int sum_array(int n, int a[*]); /* Version 2a */ int sum_array(int, int [*]); /* Version 2b */ int sum_array(int n, int a[]); /* Version 3a */ int sum_array(int, int []); /* Version 3b */
25. 为什么可以留着数组中第一维的参数不进行说明,但是其他维数必须说明呢?
26. const int*, int const*以及int *const
const 修饰后面的元素
const int * 修饰 int ,所以指针指向的值不可以改变。
int const *p 修饰 *p, *p 代表解码之后的值,即const还是修饰值,所以指针指向的值不可以改变。
int * const p , const 直接修饰指针p,即指针指向的地址不可以改变,那么指针指向不可以改变。
27. 指针的算术运算
* 指针加上整数:
指针p加上整数j产生指向特定元素的指针,这个特定元素是p原先指向的元素后的j个位置。更确切地说,如果p指向数组元素a[i],那么p + j指向a[i + j](当然,前提是a[i + j]必须存在)
* 指针减去整数:

29. 指针处理多维数组
for (int *p = &a[0][0]; p <= &a[NUM_ROWS - 1][NUM_COLS - 1]; p++) {)
* 处理多维数组的行 *
* 处理多维数组的列 *
30. 用多维数组名作为指针
int a[NUM_ROWS][NUM_COLS];

假设a是一维数组而p是指针变量。如果刚执行了赋值操作p = a,下列哪些表达式会因为类型不匹配而不合法?其他的表达式中哪些为真(有非零值)?
p[0] == a[0]
30. 如何向函数传入一个二维数组
在C语言中,你可以通过以下几种方式向函数传入一个二维数组:
1. 使用指针参数:
函数参数可以声明为指向二维数组的指针。你需要传递二维数组的首地址给函数,同时在函数参数中声明指针类型,并指定数组的列数(如果不固定,可以使用动态内存分配或额外参数传递)。
例如:
void function(int (*arr)[N], int rows) { // 使用arr来访问二维数组 } int main() { int arr[M][N]; function(arr, M); return 0; }
2. 使用一维数组参数:
你可以将二维数组转换为一维数组,并将其作为函数参数传递。在函数内部,通过计算索引来模拟多维数组的访问。
例如:
void function(int *arr, int rows, int cols) { // 使用arr来模拟二维数组的访问 } int main() { int arr[M][N]; function(&arr[0][0], M, N); return 0; }
3. 使用动态内存分配:
如果二维数组的大小在编译时是未知的,你可以使用动态内存分配来创建二维数组,并将其传递给函数。函数参数可以声明为指向适当类型的指针,并在函数内部进行内存管理。
例如:
void function(int **arr, int rows, int cols) { // 使用arr来访问二维数组 } int main() { int **arr; int rows = M; int cols = N; // 动态分配二维数组的内存 arr = malloc(rows * sizeof(int *)); for (int i = 0; i < rows; i++) { arr[i] = malloc(cols * sizeof(int)); } // 调用函数并传递二维数组 function(arr, rows, cols); // 释放动态分配的内存 for (int i = 0; i < rows; i++) { free(arr[i]); } free(arr); return 0; }
无论使用哪种方法,重要的是在函数内部正确处理二维数组的索引和大小,以确保访问和操作二维数组的正确性。
31. int *p = &a[0][0] 和 int *p = &a[0]
在C语言中,`int *p = &a[0][0];`和`int *p = a[0];`是有区别的。
1. `int *p = &a[0][0];`: 这行代码将p指向二维数组a的第一个元素的地址。p是一个整型指针,可以逐个遍历整个二维数组的元素。
2. `int *p = a[0];`: 这行代码将p指向二维数组a的第一个子数组(也就是a[0])的第一个元素的地址。换句话说,
在C语言中,`int *p = &a[0][0];`和`int *p = a[0];`是有区别的。
1. `int *p = &a[0][0];`: 这行代码将p指向二维数组a的第一个元素的地址。p是一个整型指针,可以逐个遍历整个二维数组的元素。
2. `int *p = a[0];`: 这行代码将p指向二维数组a的第一个子数组(也就是a[0])的第一个元素的地址。换句话说,p指向的是第一个子数组的开头。虽然这个表达式和上一个看起来可能会访问同一个元素,但这里的意义更多的是以一维数组的形式来处理a[0],如果你试图使用这个指针去访问二维数组a的其他子数组元素(例如a[1][0])可能会造成越界等问题。
所以这两种方式的主要区别在于,第一种方式允许你按照线性方式访问整个二维数组,而第二种方式更多的是让你能够处理二维数组的某一个子数组。
虽然这个表达式和上一个看起来可能会访问同一个元素,但这里的意义更多的是以一维数组的形式来处理a[0],如果你试图使用这个指针去访问二维数组a的其他子数组元素(例如a[1][0])可能会造成越界等问题。
所以这两种方式的主要区别在于,第一种方式允许你按照线性方式访问整个二维数组,而第二种方式更多的是让你能够处理二维数组的某一个子数组。
在C语言中,无论`p`指向何处,`*p++`都将发生两个操作:
1. 对`p`当前指向的值进行取值操作,就好像在执行`*p`。
2. 然后将指针`p`向前移动一个存储单位,这取决于`p`指向的数据类型的大小。对于`int`类型来说,`p`通常会向前移动4个字节(在大多数现代计算机系统中)。
但是,尽管`*p++`在两种情况下的行为相同,但是由于`p`的初始值不同,所以`p`遍历的元素将会不同。
1. 当`int *p = &a[0][0];`时,`p`开始指向二维数组的第一个元素。每次执行`*p++`,`p`都会移动到下一个元素,直到遍历整个二维数组。
2. 当`int *p = a[0];`时,`p`开始指向二维数组的第一个子数组(也就是a[0])的第一个元素。每次执行`*p++`,`p`都会移动到下一个元素,但只在子数组a[0]中移动。如果你继续执行`*p++`直到超出a[0]的范围,你可能会遇到未定义的行为,因为`p`可能会开始访问不属于原始数组的内存。
总的来说,`*p++`的行为在这两种情况下是相同的,但是它遍历的元素会根据`p`的初始值的不同而不同。
31. 字符数组与字符指针
chardate[]="June14"; char*date="June14";
在声明为数组时,就像任意数组元素一样,可以修改存储在date中的字符。
在声明为指针时,date指向字面串,在13.1节我们已经看到字面串是不可以修改的。
在声明为数组时,date是数组名。在声明为指针时,date是变量,这个变量可以在程序执行期间指向其他字符串。
32. 用scanf 读取字符串
scanf("%s", str);
不需要加取址符,因为str就是数组名,编译器会把他当做指针处理。
调用时,scanf函数会跳过空白字符(3.2节),然后读入字符并存储到str中,直到遇到空白字符为止。scanf函数始终会在字符串末尾存储一个空字符。
用scanf函数读入字符串永远不会包含空白字符。因此,scanf函数通常不会读入一整行输入。换行符会使scanf函数停止读入,空格符或制表符也会产生同样的结果。
33. 用gets 读取字符串
gets函数不会在开始读字符串之前跳过空白字符(scanf函数会跳过)。
gets函数会持续读入,直到找到换行符才停止(scanf函数会在任意空白字符处停止)。此外,gets函数会忽略换行符,不会把它存储到数组中,并用空字符代替换行符。
34. 字符串的复制比较
35. 字符串库 #include <string.h>
字符串复制: strcpy(str2,"abcd");
字符串长度: len = strlen(strl);
字符串拼接函数:
strcpy(str1, "abc"); strcat(str1, "def"); /* str1 now contains "abcdef" */ strcpy(str1, "abc"); strcpy(str2, "def"); strcat(str1, str2); /* str1 now contains "abcdef" */
字符串比较: if(strcmp(str1,str2)<0) /*is str1 < str2? */
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)