C尖笔记

2025.11.14

你提出的判断题“一维数组定义的一般形式如下,其中的类型名指定数组变量的类型。”的正确答案是 F(错误)

下面对此进行解释。

核心概念辨析

在一维数组的定义形式 类型名 数组名[数组长度]; 中,关键在于理解 “类型名”指的是什么

  • 正确的理解类型名 指定的是数组中每个元素的数据类型,而不是整个“数组变量”的类型。例如,在定义 int a[10]; 时,int 表明数组 a 的每一个元素(a[0], a[1], ..., a[9])都是基本 int 类型。
  • 数组变量的类型:数组本身作为一种复合数据类型,其完整的类型是由“元素类型”和“数组长度”共同决定的。以上面的 int a[10]; 为例,数组变量 a 的完整类型是“包含10个整型元素的数组类型”,这通常写作 int [10]

为了更清晰地展示这个区别,请看下表:

定义语句 类型名 的含义(元素类型) 数组变量的实际类型
float scores[30]; float float [30]
char name[20]; char char [20]

为什么这是易错点

这个判断题是一个经典的概念陷阱。它利用了“类型名”这个词汇可能带来的模糊理解。初学者很容易认为“定义变量,前面的词当然指定变量类型”,从而判断为正确(T)。但实际上,数组是一种特殊的数据结构,它包含多个相同类型的元素,因此在定义时需要首先明确其基础构建块(即元素)的类型。

希望这个解释能帮助你更好地理解数组的定义!如果对数组的其他概念还有疑问,可以随时提出。

2025.11.12

'\08'是错误的字符常量,但是实际上编译器会把它解析成一个\0和一个8,报错也是报multi-character constant不是invalid digit 8 in octal constant

验证实例:

#include <stdio.h>

int main() {
    char str[] = "\08";
    printf("Length: %zu\n", sizeof(str));  // 输出:3(包含结尾的\0)
    printf("First char: %d\n", str[0]);    // 输出:0(\0的ASCII)
    printf("Second char: %c\n", str[1]);   // 输出:8
    return 0;
}

2025.11.11

C语言数组初始化-去复习。

注意数组的sizeof,如果在作用域直接调用,就可以。

传入参数:[Warning] 'sizeof' on array function parameter 'cnt' will return size of 'int *' [-Wsizeof-array-argument](原因是传参数的时候数组会退化成指针)

2025.11.7

老师给的饱和运算的std,max溢出了也无所谓,只要保证最高位是1即可。

涉及到取出每一位数,有0的时候,要么特判,要么用do-while。

C语言貌似不能用

const int N=100;
int a[N];

注意题目没说数据范围,默认都可能……

以下能正确定义数组并正确赋初值的语句是()。

A.
int N=5, b[N][N];

B.
int a[1][2]={{1}, {3}};

C.
int c[2][ ]={{1, 2}, {3, 4}};

D.
int d[3][2]={{1, 2}, {34}};

不是的,只有第一维的大小可以省略,而且必须提供足够的初始化信息让编译器能推断出第一维的大小。

规则如下:

  1. 一维数组:可以省略长度,编译器根据初始化的元素个数确定。

    int a[] = {1, 2, 3}; // 等价于 int a[3] = {1, 2, 3};
    
  2. 二维数组:可以省略第一维(行数),但不能省略第二维(列数)。

    int b[][3] = {{1,2,3}, {4,5,6}}; // 行数自动推断为 2
    int c[][4] = {1, 2, 3, 4, 5, 6}; // 行数 = 6/4 向上取整 = 2 行
    
  3. 多维数组:只有第一维可以省略,后面所有维的大小必须明确指定。

    int d[][2][3] = { 
         {{{1,2,3}, {4,5,6}}}, 
         {{{7,8,9}, {10,11,12}}} 
    };
    

原因
C 语言的数组在内存中是按行优先存储的,为了计算每个元素的地址,必须知道除第一维外其它维的大小(即一行有多少元素、一个二维面有多少元素等)。如果省略后面的维度,编译器无法计算地址偏移。

假设int类型变量占用4个字节,定义数组int a[] = {4, 6, 8, 10};,则表达式sizeof(a)的值为 16

注意没规定a的大小。

分数 2
作者 颜晖
单位 浙大城市学院
写出以下程序段的运行结果。请注意,直接填数字,前后不要加空格等任何其他字符。

int i, max_sum, n, this_sum;
int a[ ] = {-1, 3, -2, 4, -6, 1, 6, -1};
scanf("%d", &n);
max_sum = this_sum = 0;
for( i = 0; i < n; i++ ) {
this_sum += a[i];
if( this_sum > max_sum ) max_sum = this_sum;
else if( this_sum < 0 ) this_sum = 0;
}
printf("%d\n", max_sum);
输入8,输出
7

输入5,输出
5

n=8是7,不是8;

一维数组定义时,程序在栈中只分配了数组元素的存储空间,没有分配数组名的存储空间。

何意味

int a[10] = {}; // 所有10个元素都被初始化为0

以下代码段对二维数组进行初始化。

int a[3][2];
a = { {1, 2}, {3, 4}, {5, 6} };

要在定义的时候初始化,所以是错!!!

定义二维数组int a[][3] = { 1, 2, 3, 4, 5, 6 };,则sizeof(a[0])的值为sizeof(int) * 3。

true

2025.11.6

image

服了,得端正学习态度了……

课后作业6函数题1:又是边界问题,n=1要特判

两个字节是16位!!!

%5.2f要补空格!

sizeof里面的变量操作不会被执行

... ? ... : ... 是右结合的,比如a < b ? a : b > c ? b : c等价于a < b ? a : (b > c ? b : c),然后它如果a<b,就直接返回a了,不会去算(b>c?b:c)

逗号运算符会返回最右边的值,条件优先级大于赋值大于逗号。

... ?... :... 会提升变量类型,两个结果会隐式类型转换。

以下程序段统计整型变量x的二进制表示中1的个数。

int x, count = 0;
scanf("%d", &x);
for (; x; x &= x - 1)
    count++;

定义变量double n; 表达式sizeof(n > 0 ? 1 : -1.f)的值为sizeof(double)

a?b:c的size只和bc有关,与a和表达式的值无关。sizeof也不会具体计算表达式的值。b,c类型不同,会进行简单的算术类型提升。

错题:

以下代码实现输入一行字符,将大写字母转换为相应的小写字母后输出,其他字符原样输出。

while (ch = getchar() != '\n') {
    if (ch >= 'A' && ch <= 'Z')
        ch = ch - 'A' + 'a'; 
    putchar(ch);
}

没加括号,ch变成布尔值了……

真得好好复习表达式求值了啊!!!

image

image

2025.11.2

写课后作业6 7-3被坑了,最后是空格的时候,最后一个单词已经输出过了……,最好的方法是在最后补上一个空格

以后要注意讨论开头和结尾有无空白字符

空格要和换行一起考虑,题目就是没说清楚……

绝对的恶心毒瘤好题。

史题。

还有一个坑,就是pta的样例貌似其实会有换行,但是并不会显示出来


  1. putchar如果成功输出了字符,会返回这个字符。否则返回EOF。
  2. 如果for的循环体语句中没有使用continue语句,则以下for语句和while语句等价。

for (表达式1; 表达式2; 表达式3)
for的循环体语句
表达式1;
while (表达式2) {
for的循环体语句;
表达式3;
}

用了continue另当别论!

  1. 类型转换,大转小直接截断低位;小转大:无符号整数直接高位复制0,有符号复制符号位(最高位),这样的结果就是数字的值不变,变成了更大字节下的原值的补码。

浮点数的截断也是类似的。

  1. 注意while循环有时候没有自加。

若变量已正确定义,以下while循环结束时,i的值为11。

i = 1;
while (i <= 10){
    printf("%d\n", i);
}

这个是错的。

  1. 设变量已正确定义,选项( )与以下程序段不等价。

switch(op){
case '+': printf("%d", value1 + value2);
default: printf("Error");
case '-': printf("%d", value1 - value2);
}

A.
if(op == '+'){
printf("%d", value1 + value2);
printf("Error");
}else if(op != '-'){
printf("Error");
}
printf("%d", value1 - value2);

这俩等价……

    • ​​当 switch在循环内部时​​在循环内部的 switch语句中使用 continue会​​跳过当前循环迭代的剩余部分​​,直接开始下一次循环迭代。
  • 当 switch不在循环中时​​,如果 switch语句不在任何循环内部,使用 continue会导致​​编译错误​​,因为 continue只能在循环结构中使用。

break在switch里面只是跳出switch,但是continue是跳到循环下一步。

for(i = 11; i <= 20; i++){ 
    for(i = 1; i <= 10; i++){
        printf( "%4d", i );
    }
    printf("\n"); 
}

看起来好像里面的i会覆盖掉外面的定义变成局部变量,不会死循环,对吧?

实际上,它是一个i,没有重新定义,所以会死循环。

  1. 实参是后面先计算

  2. \(1 + 3 + ... + 2m-1 = m ^ 2\)

  3. 程序内存和机器内存不一样

常量Constan区和代码Code区都只读,不能写入,不然会报段错误。

栈是从地址高的地方开始分配。

  1. ~x+1表示-x,但是x=0

注意负数的反码是符号位不变,其它位取反,但是C的~运算是不管符号位的,是直接所有位取反。但是~5+1,因为5本身是正数,所以~之后刚好是-5的反码,所以得出来就是-5的补码。

  1. 注意统计0的位数相关问题都要特判或者dowhile。

  2. 负数取出每一位之后本身就是负数,如果要统计位数需要取绝对值或者相反数,但是如果是反转数字符号不变反而不用特殊处理。

详见课后作业5函数题第四题。

  1. 可以把样例复制到代码框观察空格

  2. 函数是一个完成特定工作的独立程序模块,包括库函数自定义函数两种。

  3. void income(double number), expend(double number);expend函数申明省略了函数类型,其函数类型是默认的int

根据测试,expend函数也是void类型,这道题答案是F。

  1. 以下循环代码将导致死循环。
int i;
for (i = 1; i; i++)
  ;

好像并不会。

  1. double不光精度更高,取值范围也更大。

  2. 浮点数整数部分的0可以省略,比如0.1可以写成.1。

没有后缀是double,有f、F后缀是float,有l、L后缀是long double。(long double精度一般80位,但是字节不一定是多少,只是规定比double大)

屏幕截图 2025-10-24 211342

n>=0错了,因为n==0还会继续减,应该是n>0。无论何时都要检查等号!

屏幕截图 2025-10-24 211727

这种打印题一定要仔细看,推式子!比如下半部分本来写for (j=2;j<=i;j++)打空格,就只对了一行,下一行就错了!

  1. goto语句可以到do-while循环体,但是while和for不行,因为do-while是先执行一次再判断条件是否成立。

到底这个是while和do-while主要区别,还是至少执行一次循环体,去看看答案。(课后作业5,2-2)

  1. 2 3 5 7 ,9不是质数!

注意16进制,如果是小写x,输出小写字母的十六进制数,大写X输出大写字母。

屏幕截图 2025-10-24 212228

  1. 8进制数不能出现大于等于8的数字!!!081会编译错误。

  2. 循环不光可以break退出,还可以returngoto或者表达式不满足条件自然退出。

以下关于函数叙述中,错误的是( )。

A.函数未被调用时,系统将不为形参分配内存单元

B.实参与形参的个数必须相等,且实参与形参的类型必须对应一致

C.当形参是变量时,实参可以是变量、常量或表达式

D.如函数调用时,实参与形参都为变量,则这两个变量不可能占用同一内存空间

正确答案是 B. 实参与形参的个数必须相等,且实参与形参的类型必须对应一致

详细解析:

  1. A. 正确
    函数未被调用时,形参只是声明,系统不会为其分配内存单元。只有当函数被调用时,才会为形参分配临时的内存空间(栈空间)。

  2. B. 错误 ?
    这是错误的叙述,原因如下:

    • 可变参数函数(如printf)的实参个数可以不等于形参个数
      // printf的形参只有1个(format字符串)
      // 但实参可以有多个(如%d对应的值)
      printf("%d %f", 10, 3.14); 
      
    • 类型兼容性:实参类型可以自动转换为形参类型(如int传递给double)
      void func(double x) { /*...*/ }
      func(10);  // int自动转为double
      
  3. C. 正确
    当形参是变量时:

    • 实参可以是变量:func(a);
    • 实参可以是常量:func(10);
    • 实参可以是表达式:func(a + b * 2);
  4. D. 正确
    在传值调用(非指针)时:

    • 形参是实参的副本,占用独立的内存空间
    • 即使同名,也是两个不同的变量
    void change(int x) {  // x是形参
        x = 20;          // 修改不影响实参
    }
    int main() {
        int a = 10;      // 实参a
        change(a);       // a和x占用不同内存
        printf("%d", a); // 输出10(未改变)
    }
    

关键总结:

  • B选项的错误在于忽略了C语言的两个重要特性:
    1. 可变参数函数(实参个数可多于形参)
    2. 隐式类型转换(实参类型可自动转换为形参类型)

因此B是唯一错误的叙述。其他选项均正确描述了C语言的函数参数机制

  1. 在C语言中,像 a = 10 中的常量 10 的存储位置取决于它的使用场景和上下文。以下是详细的解释:

常量 10 的存储位置

  1. 立即数(Embedded in Instructions)

    • 在大多数情况下,像 10 这样的小整数常量会直接嵌入到机器指令
    • 编译器会生成类似 MOV [a], 10 的指令,其中 10 是作为指令的一部分存在
    • 这种情况下,常量不占用独立的内存空间,而是存储在代码段(text segment)
  2. 只读数据段(.rodata)

    • 如果常量被用于全局初始化(如全局变量初始化)或作为字符串常量
    • 会被存储在只读数据段(read-only data segment)
    • 例如:
      const int global_const = 10;  // 存储在.rodata
      
  3. 静态存储区(static storage)

    • 当常量用于初始化静态变量时:
      static int x = 10;  // 10作为初始化值,但x本身在.data段
      
    • 这里的 10 是初始化值,会被编译器处理:
      • 可能作为立即数嵌入指令
      • 或存储在.rodata中(如果编译器需要单独存储)
  4. 栈空间(stack)

    • 对于局部变量初始化:
      void func() {
          int a = 10;  // 10作为立即数嵌入指令,a在栈上
      }
      
    • 常量 10 不占用栈空间,而是作为指令的一部分

关键总结

使用场景 存储位置 是否占用内存
局部变量初始化(如 int a = 10; 代码段(作为指令的一部分) ❌ 不单独占用
全局变量初始化(如 int x = 10; .rodata 或 代码段 ✅ 可能占用
静态变量初始化(如 static int y = 10; .rodata 或 代码段 ✅ 可能占用
常量表达式(如 case 10: 代码段 ❌ 不单独占用
字符串常量(如 "hello" .rodata ✅ 占用

特殊说明

  1. 小整数优化

    • 编译器通常对小整数(如 10)进行优化,直接嵌入指令而不分配内存
    • 这比访问内存更高效
  2. static 区的区别

    • static 区(.data/.bss)存储的是变量(如全局变量、静态变量)
    • 常量本身通常不存储在 static 区,除非它是:
      • 字符串常量(在.rodata)
      • 或用于初始化静态/全局变量(值可能被复制到.data)
  3. 验证示例

    #include <stdio.h>
    
    const int c = 10;  // 在.rodata
    
    int main() {
        static int s = 10;  // 初始化值可能在.rodata,s在.data
        int a = 10;         // 10是立即数(在代码段)
        printf("%p %p", &c, &s); // 有地址
        // printf("%p", &10);    // 错误!字面值常量没有地址
    }
    

因此,在 a = 10 这样的语句中,常量 10 通常不存储在 static 区,而是作为指令的一部分直接嵌入代码段。只有在特定场景(如全局常量定义)才会存储在只读数据段(.rodata)。

  1. 以下正确的说法是()。

A.实参与其对应的形参共同占用一个存储单元

B.实参与其对应的形参各占用独立的存储单元

C.只有当实参与其对应的形参同名时才占用一个共同的存储单元

D.形参是虚拟的,不占用内存单元

答案B

  1. 感觉问DS单个问题还好,问题一多DS就变成sb了。。。搞得我课后作业4错这么多……

AI在复杂程序还是容易错……,以后还是先给题让它自己做,不要让它验证,要像选择题一样,他给个答案,我再去对

  1. 在浮点数表示中(如IEEE 754标准),“溢出”通常指的是“上溢”,而这种情况确实主要是由阶码溢出引起的。 但“溢出”也可以指“下溢”,这则不是阶码溢出。

让我们详细分析一下:

  1. 上溢:

    • 原因: 当计算结果的绝对值太大,以至于超过了浮点数格式所能表示的最大有限数值时,就会发生上溢。
    • 阶码的作用: 浮点数的阶码决定了数值的指数部分。阶码有一个最大值(对于单精度float是127)。
    • 阶码溢出: 如果计算结果的指数部分需要比最大阶码值还大的阶码来表示,那么阶码本身就无法容纳这个值。这就是阶码溢出。
    • 结果: 发生上溢时,计算结果会被表示为正无穷大负无穷大(取决于符号位)。阶码的所有位会被设置为1(表示特殊值),尾数部分被设置为0。
    • 结论: 上溢本质上就是阶码溢出。 阶码无法表示所需的过大指数。
  2. 下溢:

    • 原因: 当计算结果的绝对值太小,小于浮点数格式所能表示的最小规格化正数时,就会发生下溢。
    • 阶码的作用: 阶码有一个最小值(对于单精度float是-126)。
    • 阶码“溢出”? 下溢发生时,计算结果需要的指数比最小阶码值还小(更负)。这看起来像是阶码“向下溢出”。
    • 实际处理: 然而,IEEE 754标准通过引入非规格化数来处理这种情况。当阶码达到最小值后,如果结果继续变小,计算机会开始使用非规格化数表示(阶码固定为最小值,尾数不再隐含前导1,而是允许前导0)。这允许表示比最小规格化数更接近0的值,但精度会逐渐损失。
    • 真正的下溢: 当计算结果小到连非规格化数都无法表示时(即尾数下溢),结果会被刷新为0(带符号)。
    • 结论: 下溢通常不是由阶码“溢出”引起的,而是阶码已经达到最小极限后,尾数无法再表示更小的值。 阶码本身并没有“溢出”其位域(它被固定在最小值),是数值的绝对值太小了。
  3. 尾数溢出:

    • 在浮点数加减运算中,对齐阶码后尾数相加/减时,可能会发生尾数进位溢出(例如,1.0 + 1.0 = 10.0)。
    • 处理: 这种情况通过右规格化来处理:尾数右移一位(相当于除以2),阶码加1(相当于指数加1)。
    • 结果: 只要阶码加1后没有超过最大值,这个操作就能修正尾数溢出,结果仍然是有效的规格化数。
    • 与溢出的关系: 如果尾数溢出后,执行右规格化导致阶码超过了最大值,那么最终的结果溢出仍然是阶码溢出(上溢)导致的。

总结:

  • 上溢:阶码溢出的直接结果。数值太大,阶码无法表示所需的指数。
  • 下溢: 不是阶码溢出。数值太小,阶码已达到最小极限,尾数也无法表示该值(即使在非规格化形式下),最终结果变为0。
  • 尾数溢出: 是中间计算步骤,可以通过右规格化修正,除非修正导致阶码溢出(上溢)

因此,当你听到“float溢出”时,如果指的是数值变得过大(例如 1e38 * 10),那么答案就是是的,这主要是阶码溢出(上溢)。如果指的是数值变得过小(例如 1e-38 / 10),那么它属于下溢,不是阶码溢出

  1. char当成有符号的,unsigned char当成无符号的。

  2. float精度是,double精度是……位

屏幕截图 2025-10-29 111824
血泪史

posted @ 2025-10-14 22:22  Zlc晨鑫  阅读(18)  评论(0)    收藏  举报