备考c语言选择题(持续更新)25.3.2

一些提醒

一定要看清楚代码块的包含关系:

1
2
while(*p++!='\0');//这里while循环已经结束了!!!!!!!!!
return(p-s);

3.2

合法标识符

  • 记住:下划线可以开头,数字不可以void这种关键字也不行。

赋值

  • 记住:一定要有分号,赋值末尾

    该语句i++为合法赋值语句:相当于i = i + 1

  • 赋值运算符从右到左,赋值号左边:**代表某个存储单元的变量名,**右边:C语言中合法的表达式

  • 比如:

强制类型转换:

  • 在 C 语言中,强制类型转换是通过类型转换运算来实现的,其一般形式为:(类型说明符)(表达式) ,功能是把表达式的运算结果强制转换成类型说明符所表示的类型。

    • 示例(float)a可把变量a转换为浮点型;(int)(x + y)会把x + y的结果转换为整型。

    • 注意事项

      • 类型说明符和表达式都必须加括号(单个变量可以不加括号) 。例如(int)(x + y)若写成(int)x + y,就变成先把x转换成int型,之后再与y相加。
    • 无论是强制转换或是自动转换,都只是为了本次运算的需要,对变量的数据长度进行临时性转换,不会改变数据说明时对该变量定义的类型。例如float f = 5.75;(int)f会得到 5(删去小数部分),但f本身的值仍为5.75

    • 赋值中的类型转换

      :当赋值运算符两边的运算对象类型不同时,会发生类型转换,把赋值运算符右侧表达式的类型转换为左侧变量的类型。

      • 浮点型与整型:浮点数转换为整数时,舍弃小数部分,只保留整数部分;整型值赋给浮点型变量,数值不变,以浮点形式存储。
    • 单、双精度浮点型float型数据会延长为double型数据参加运算,再直接赋值;double型数据转换为float型时,通过截尾数实现,截断前会进行四舍五入。

      • char 型与 int 型int型数值赋给char型变量时,只保留最低 8 位,高位部分舍弃;char型数值赋给int型变量时,若char型数据值大于 127,不同编译程序处理方式有差异,若原值为正,转换后仍为正,原值可正可负,则转换后保持原值,只是内部表示形式不同。
      • int 型与 long 型long型数据赋给int型变量时,截断高 16 位,只保留低 16 位(假定int型占两个字节);int型数据赋给long型变量时,外部值不变,内部形式改变。
      • 无符号整数:将unsigned型数据赋给同样长度存储单元的整型变量,或反之,内部存储形式通常不变,但外部值可能改变

    此外,在函数的参数传递、返回值处理,以及单片机或 Linux 驱动开发中的寄存器地址使用等场景,也常需要进行强制类型转换。

    双等号赋值:

    sum = pad =5,5同时给sumpad两个值赋值。

自增运算

例题一:

  • 在 C 语言中,pAd++++pAd都属于自增运算符,但使用方式和效果有所不同:

    • pAd++(后置自增):先使用pAd当前的值参与表达式运算,运算完成后,pAd的值再自增 1。例如int a = pAd++; ,会先把pAd原来的值赋给a,然后pAd自身加 1。
    • ++pAd(前置自增)pAd的值先自增 1,然后再使用自增后的值参与表达式运算。比如int b = ++pAd;pAd会先加 1,再把新的值赋给b
    • 新提醒:这里的运算不止是对其他值赋值
    • 如例题二:
    • 以例题二中的代码为例:第一个i++,在printf执行完毕以后,才进行赋值,所以第一个printf输出的值是1,未变。

    以例题一题目中的代码为例,pAd++是先让pAd以原值参与逗号表达式运算,然后自身加 1;++pAd则是先让pAd加 1,再以新值参与后续运算。

    • 在这题中:
    • 先让pAd的值被赋值为sum的值(5),然后sum自己再增加1,
    • 后方就是pAd的变化,与pad无关
    • 并且c语言要区分大小写
  • 例题2:

  • 这里最难的就是:return的时候,++后自增运算符也会起作用,这里体现自增运算符功能如下:

  • 在第二个data=change(&data)中,data值为124,而在函数中,return(*data)++这个表达式的含义是:先将data指针解包后的数据返回,即给变量赋值后,再自增1,所以在这里,data得到的值为124,即函数先进行了return的运算,之后*data再自增1,变为125,但是变量data的值为124

整型表达式

  • C 语言中,表达式的类型由其运算结果的数据类型决定。sizeof(double)用于获取double类型在内存中所占的字节数,其返回值是一个整数,基于这个结果,就可以将sizeof(double)看作是一个整型表达式 。 类似的,像3 + 5这样的算术运算,结果也是整数,所以它同样属于整型表达式。

有关字符%

数学库的函数积累(这里一直更新)

squart函数

在 C 语言中,sqrt函数属于数学库函数,用于计算一个非负实数的平方根。使用时需注意以下几点:

  • 头文件引用:在代码中使用sqrt函数前,必须包含<math.h>头文件,因为该函数的声明在此头文件中。示例代码如下:
1
2
3
4
5
6
7
8
9
#include <stdio.h> 
#include <math.h>

int main() {
double num = 9.0;
double result = sqrt(num);
printf("%.2f 的平方根是 %.2f\n", num, result);
return 0;
}
  • 参数与返回值类型sqrt函数的参数类型通常为double型,返回值也是double型。即便传入一个整数,也会被隐式转换为double类型进行计算。比如sqrt(4),这里4会先转换为4.0再计算,返回值是2.0
  • 注意事项:该函数要求传入的参数必须是非负的。如果传入负数,在不同编译器和运行环境下可能有不同处理,常见的是返回一个 “非数”(NaN),或者引发运行时错误 。

有关数据类型的运算

  • 例题:

  • 解析:

    1
    z=( a+b )/c + sqrt(y) * 1.2 / c + x;

    根据运算符优先级,先计算括号内的a + b,结果为5;然后(a + b) / c5 / 2,因为abc都是整型,所以执行整数除法,结果为2
    接着计算sqrt(y)y的值为4.0sqrt(4.0)结果为2.0 ;然后sqrt(y) * 1.2 / c2.0 * 1.2 / 2,先算乘法得2.4,再算除法得1.2
    最后将前面的结果与x相加,即2 + 1.2 + 10.5,得到13.7,并将结果赋值给z。也就是说,c语言对于doubleint类型在运算时,要先把int转化为double

    关于数据类型和转义字符串

    这道题重点围绕 C 语言中字符数据类型和转义字符的知识展开。以下是进一步的讲解:

    • 字符数据类型存储原理:在 C 语言里,char类型用于存储字符,本质上是存储该字符对应的 ASCII 码值,占用 1 个字节(8 位)内存空间。像char c = '\72';\72这个转义字符会被转换为对应的 ASCII 码值存到c中。

    • 八进制转义字符与 ASCII 码转换\72作为八进制转义字符,要先转成十进制来对应 ASCII 码。八进制的72 ,按位权展开7 * 8^1 + 2 * 8^0 = 58 ,十进制 58 在 ASCII 码表中代表字符: 。也就是说,c实际存的是字符:的 ASCII 码值。

    • 易错点剖析

      • 很多人容易把\72误认成两个字符72,但在 C 语言转义字符规则里,它是一个整体代表单个字符。
      • 对转义字符规则不熟悉,不清楚以\开头,后跟0 - 7数字是八进制转义字符,就可能觉得语句不合法。

    通过这道题可以看出,掌握 C 语言中字符常量、转义字符以及 ASCII 码的对应关系很重要,在处理字符相关编程和题目时才不会出错。

    • 关于转义符:
    • 一个转义符/加上另外一个任意字符就算是一个字符:
    • 比如:\"或者\\\n,这三个都只是算一个字符。

循环

while循环

do whilewhile do
  • 直接看例题:

选择

switch选择

  • 例题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #include <stdio.h>

    void main()
    {
    int i=1,k=0;
    for( ; i<6 ; )
    {
    switch(i%3)
    {
    case 0: k++;
    case 1: k++; break;
    case 2: k++; continue;//这里的continue会让switch选择结构重新到for循环处。
    }
    i+=1;
    }
    printf("%d\n",k);
    }
  • 以上程序的执行逻辑:

    • 首先i%3的值为1,在case 1: k++;break;这个地方跳出switch循环。
    • 之后i变为2,此时在case 2这个位置开始执行代码,continue语句使得代码重新返回for循环开头,
    • 此时可以发现,i的值为2,会一直在case 2这个语句发生continue。无限循环。
    • 我们值得注意的就是continue优先执行在for或者while这样的循环语句中,在这道题里面,continue针对于for进行跳出。
    • 所以给我们造成了一个错觉,这里的continue似乎是把switch语句跳过了,其实本质上还是回到for循环开头并且没有执行for循环里面continue后面的语句.

运算符

算术运算符

  • %运算符

    • 在 C 语言中,%是取模运算符,用于计算两个整数相除后的余数 ,其运算规则为:a % bab 为整数,b≠0)的结果是 a 除以 b 得到商后剩下的余数。

      对于2 % 92除以9,因为2小于9,不够除,此时商为0 ,根据 “余数 = 被除数 - 商 × 除数” 的公式,即2 - 0×9 = 2 ,所以2 % 9的结果是2这里太重要了

    • 记住:商为0的时候,余数就为被除数(这里是2)它本身

执行顺序
  • 例题
  • 这里我们要考虑结合性,即程序是从左到右运行的。

逻辑运算符

关系运算符
  • b = (-1)&&(-1)
    
  • 这种情况下,b值为1(True)。

  • 例子:

1
2
3
4
5
6
#include <stdio.h>

int fun(char *s, char *t) {
while ((*s) && (*t) && (*t++ == *s++));//这里是先将t和s所指向的第一个字符进行比较之后再后移一位。
return (*s - *t);//返回两后移一位的字符的ASCII🐎的差值。所以是在比较大小。
}
位运算符
  • 有关异或运算符^的例子:

  • 假设两个十进制数 A = 10,B = 5,来看看它们进行异或运算的过程:

    1. 先将它们转换为二进制数,10 的二进制是 0000 1010,5 的二进制是 0000 0101
    2. 然后按照异或运算规则逐位运算:
      • 第 1 位(从右往左):0 ^ 1 = 1
      • 第 2 位:1 ^ 0 = 1
      • 第 3 位:0 ^ 1 = 1
      • 第 4 位:1 ^ 0 = 1
      • 第 5 - 8 位:0 ^ 0 = 0
    3. 得到的结果是 0000 1111,转换为十进制就是 15 。

    再比如,设 C = 8(二进制为 0000 1000),D = 8(二进制为 0000 1000),它们进行异或运算时,逐位对比:

    • 每一位都是相同的数字,按照规则 0 ^ 0 = 01 ^ 1 = 0,所以结果是 0000 0000,即十进制的 0。
  • 什么时候判别位0,什么时候判别为1呢?

  • 具体来说

    • **结果为0的情况:**在对应的两位数字相同的时候,如1^1或者0^0的时候返回值为0
    • **结果为1的情况:**当两个相应位的值不同时,异或运算结果为 1
  • 更多位运算符请详见菜鸟教程——C运算符

有关运算顺序
  • 该例题可以说明情况:

指针

基础1

  • 简单来说指针:p,就是对一个变量进行指向,让我们后续找到这个变量更加方便

  • *p就是解析指针,p就是直接输出指针的值,&就是对一个变量的地址进行抓取。

  • 如下:

  • #include <stdio.h>
    
    int main(){
        int a = 6;
        int * p;
        char * pi;
    
        p = &a;
        printf("%d, %d\n", *p, p); // prints the address of a; 
    }
    
  • 会分别输出:66291076,前者就是变量具体的值,后者是其地址的值。

  • 指针的用法就是对一个变量的地址进行追踪:

  • p = &a,然后*pp分别是对指针解析与不解析,解析了就为变量具体值,不解析就为地址值,指针的存在方便我们去最总一个变量,修改变量的值。

  • 比如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <stdio.h>

    int main(){
    int a = 6;
    int * p;
    char * pi;

    p = &a;

    *p = 7;
    printf("%d",a);
    }
  • 如上代码,先把p*号解包,代表此时指针是具体值,具体值的值发生改变,那么所指向的值也会改变。此时输出值为7

基础2

  • 指针必须要先指向一个地址,有内存空间的指向才可以接收其他的值

  • 比如:

  • #include <stdio.h>
    
    void swap( int *p,int *q)
    { int *t;
    *t=*p; *p=*q; *q=*t;
    }
    //main()略..
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237

    * 这里的指针变量`t`虽然已经声明,但是没有指向任何一个内存地址,不可以用来存放其他变量的值

    * 比如:

    * ```c
    #include <stdio.h>
    #include <stdlib.h>

    void swap(int *p,int *q)
    {int *t;
    t = (int *)malloc(sizeof(int));
    *t = *q,*p = *q,*q = *t;
    }

    * 如该代码,指针变量`t`初始已经指向了一个动态分配的地址,可以用于接收变量。

    #### 指针的赋值

    * C语言中专辑可以把变量地址赋值给同类型的指针变量,**不可以把变量和表达式地址赋值给指针**
    * 可以通过类型强制转换把一种类型的指针变量赋值给另外一种类型的指针变量。

    #### 函数中的操作

    * 比如:

    * ```c
    #include <stdio.h>

    void swap(int * a,int * b){ //这里的'*'符号,只是代表我们正在定义指针
    int t = *a;
    *a = *b;
    *b = t;
    }

    int main(){
    int a = 6;
    int b = 3;

    printf("%d,%d\n", a,b);
    swap(&a, &b);//此时传入的值是指针
    printf("%d,%d", a,b);

    }

    * 输出的结果为:

    * ```powershell
    6,3
    3,6

    * 我们可以进一步说明:

    * ```c
    #include <stdio.h>

    void swap(int a,int b){ //这里的''符号,只是代表我们正在定义指针
    int t = a;
    a = b;
    b = t;
    }

    int main(){
    int a = 6;
    int b = 3;

    printf("%d,%d\n", a,b);
    swap(a, b);//此时传入的值是指针
    printf("%d,%d", a,b);

    }

    * 比如上述代码,就和我们的第一组代码有差异,这里没有使用指针对变量地址进行赋值,所以输出的值为:

    * ```powershell
    6,3
    6,3

    * 你可以发现,它的值并没有进行调换,所以:**只有修改变量地址才是真正的修改变量的值**。

    #### 数组中的操作

    ##### 基础概念:

    * * 数组是一个常量:

    * ```c
    #include <stdio.h>

    int main(){
    int a = 6;
    int b = 3;
    int c[3] = {1,2,3};

    c = &a;

    printf("%d", c);

    }

    * 像这样进行赋值,就会报错。

    * 对于数组的本质

    * * 简单来说,**数组和指针的本质区别就是,前者为常量,不可以赋值,后者为变量,可以被赋值,**其他操作方式其实都大同小异。

    * 比如我们可以看以下代码:

    * ```c
    #include <stdio.h>

    int main(){
    int a = 6;
    int b = 3;
    int c[3] = {1,2,3};

    printf("%d", *c);

    }

    * 这里就是对类似于指针的数组`c`进行了解包,但是默认解包出来的数是数组的第一个数,即`1`,若要对下一位数进行解包,可以这样进行解包:`printf("%d", *(c+1))';`会输出:`2` 相当于:`+1`是为了移位,`*`是为了解包。

    * 这里我们可以知道一个规律是怎么来的了:**为什么编程语言的索引都是从0开始?**因为,在c语言中;对于数组`c`,`*(c+1)`和`c[1]`都是一个道理,为了保证形式上的相同,所以这样干。**数组索引不可以使用'()'**

    * 我们这里可以继续说:

    * ```c
    #include <stdio.h>

    int main(){
    int a = 6;
    int b = 3;
    int c[3] = {1,2,3};

    int * p = c;

    printf("%d", p[1]);

    }

    * 这里一样可以把指针`p`当成数组进行索引分配。输出`2`。

    ##### 指针数组:

    * {% asset_img image-20250302175735900.png image-20250302175735900 %}

    * 这里我们出现了疑问:为什么我们使用int a = 6;int * p = a就会出错,而使用b = {1,2,3};int * p = b;就不会出错呢,数组就不出错?

    * 原因如下:

    * `int a = 6; int *p = a;` 出错原因

    - `int *p` 声明了一个指针变量`p`,指针变量是用来存储内存地址的,它要求赋值给它的应该是一个合法的内存地址。
    - 而`int a = 6;` 定义了一个整型变量`a`并初始化为`6`,`6`只是一个普通的整数值,并非内存地址,所以直接将`a`的值赋给指针变量`p`,会导致类型不匹配错误,编译器会报错。正确的做法应该是将`a`的地址赋给`p`,即`int *p = &a;`,这里`&`是取地址运算符,`&a`表示获取变量`a`在内存中的地址。

    * 数组情况不出错的原因。

    - 在 C 语言中,数组名在表达式中使用时,会被隐式转换为指向数组首元素的指针。当定义`int b[] = {1,2,3};` 时,`b` 代表数组`b`的首地址,它的类型本质上是`int *` (指向`int`类型的指针) 。
    - 所以`int *p = b;` 这种写法是将数组`b`的首地址赋值给指针变量`p`,两者类型匹配,因此不会出错,此时`p`就指向了数组`b`的第一个元素,可以通过`p`来操作数组元素,比如`p[0]` 等价于`b[0]` ,都表示访问数组的第一个元素`1`。

    * **这里我们可以进行一个总结:**

    * * 在 C 语言中对于数组`a[3]` ,在很多情况下数组名`a`会被隐式转换为指向数组首元素的地址:

    - **赋值场景**:当把数组名赋值给指针变量时,如`int *p = a;` ,这里数组名`a`就被当作指向数组首元素`a[0]`的地址,将该地址赋给指针`p` ,使`p`也指向数组首元素。
    - **表达式场景**:在涉及地址计算和操作的表达式中,例如`a + 1`,这里`a`同样被视为指向数组首元素的地址,`a + 1`表示指向数组第二个元素`a[1]`的地址 。
    - **函数参数传递场景**:当数组作为函数参数时,比如定义函数`void func(int arr[])` ,调用`func(a)` ,实际上传递给函数的是数组`a`首元素的地址,在函数内部`arr`就相当于一个指向传入数组首元素的指针。

    不过要注意,数组名在`sizeof`运算符中例外,`sizeof(a)`得到的是整个数组占用的字节数,此时`a`不被当作指针,而是代表整个数组 。

    * 对于指针与数组:

    * * 比如:`int *p = a `,这里也是把数组`a`首位元素地址赋值给了新定义的整型指针`p`,然后我们就可以用:`a[i]`以及`*(a+i)`、`*(p+i)`、`p[i]`这些方法对元素进行移动要注意:没有`*a[i]`或`*p[i]`的写法。

    #### 指针数组与高维数组的详细理解

    * 例子:

    * ```c
    #include <stdio.h>

    int main(){
    int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}}, *ap[3];

    for(int i=0; i<3; i++){
    ap[i] = a[i];
    }

    printf("%d", **(ap+1));//这一行可以改为:*(ap+1),ap[1],*ap[1],*(ap[1]+1)

    return 0;
    }

    * 首先对于`for(int i=0; i<3; i++)`这行代码:

    * * 在高维数组中(比如这里的二维数组),这里a[i]就是第i行的第一个数字的地址。
    * 而ap[i]是一个指针,(类型:`*int`)在这里,就把就是第i行的第一个数字的**地址**赋值给了`ap[i]`,如果是字符串的话,比如:`str[2][10] = {"aaa","bbbbb"}`,这种,要让指针数组(`strp`),指向数组:`strp[i] = str[i]`,那么这里指针代表的是蒂耶戈字符串所有的地址。
    * 而`ap`代表的就是指向元素为指针的数组的首位元素,即指针的指针:类型:`**int`,所以,要进行两次解包才行:`**(ap+1)`
    * 所以ap[1]呈现出来的是一个地址而非具体值,因为数组`ap`的元素是指针,指向了地址,这里就和一维数组不一样,一维数组使用这样的中括号索引就是具体值,而在指针数列里,这里的具体值则是地址,所以,我使用`*ap[1]`就会得到具体值





    #### 指针数组以及指针的指针

    ##### 指针数组

    * 比如:
    * {% asset_img image-20250303075709855.png image-20250303075709855 %}
    * 使用语法`char *s[6]`就是定义一个指针数组并且对其初始化,6个指针分别对应6个字符串。

    #### 自定义数据结构里面的指针

    * 有以下例题:
    * <style>.yvfknteurybu{zoom:150%;}</style>{% asset_img yvfknteurybu image-20250305135039466.png '"""image-20250305135039466"' %}
    * 要点一:对于`struct st a[3] = {5,&a[0], 6,&a[1], 7,&a[2]}, *p;`这里由于该结构在初始化的时候,需要两个值来初始化,所以`5,&a[0]`就构成了数组`a`的第一个元素`a[0]`的初始化内容 ,`6,&a[1]` 、`7,&a[2]`分别对应`a[1]`和`a[2]`的初始化内容。
    * 要点二:这里对`st`数据结构数组的值的导入,比如:`5,&a[0]`,这里第二个值为`&a[0]`,就是对`st *next`的赋值,即指向的下一个元素是它本身。
    * 要点三:指针的数据结构必须和要指向的目标元素的类型相同,才可以进行指向。
    * 要点四:`p=&a[0]`,这里就是指向数组a的第一个元素的的地址,包含:`(int n ,struct st *next)`,我们对指针进行移动:`(++p)`,再进行指向:`->`,**其中:`->`这个方法就已经对数据结构其中的元素进行了解包**,所以不需要`*p`对数据解包了。

    #### 指针在动态内存中的操作

    * 如例题:

    * ```c
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    void main()
    {
    char *p1,*p2;
    p1=p2=(char *)malloc(sizeof(char)*10);
    strcpy(p1,"malloc");
    strcpy(p2,p1 + 1);
    printf("%c%c\n",p1[0],p2[0]);
    }
  • 问:最后输出的值是什么

  • 解析:

  • 程序执行过程为:定义两个字符类型指针 p1 和 p2,动态开辟 10 个字符类型的内存单元,并且使指针 p1 与 p2 均指向这 10 个内存单元的第一个单元。调用 strcpy 函数,将字符串 “malloc” 赋值给这 10 个内存单元的前 7 个单元,存储情况为: malloc\0,此时 p1 指向 10 个内存单元的第一个单元。再次调用 strcpy 函数,这 10 个内存单元的第 2 个单元到第 7 个单元元素重新赋值给 p2 指向的内存单元以及以后的 5 个单元,存储情况为: alloc\0\0,此时 p1 和 p2 均指向 10 个内存单元的第一个单元。输出两个指针指向单元的数据值,结果为: aa,A 选项正确。

  • 关键:

    • p1p2指向同一片地址,请牢记,p2在之后发生了改变,由于p1,p2都指向了同一片地址,所以p1也会相应发生变化。
    • 另外,p1p2都默认指向malloc内存空间的起始地址。

3.3

数组

数组长度特性

  • 比如该题:
  • 如解析所示,不多说

关于不同初始化时数组的长度

1
2
char s1[]="0123";
char s2[]={'0','1','2','3'};
  • 这里说一下直接定义字符串的本质:
  • 直接定义字符串c语言就会在字符串数组后面默认加上一个停止符’\0’,比如第一行代码;而以字符组的形式定义字符串,就需要手动加上停止符,比如第二行代码(这里它没有手动加上停止符)。
  • **strlen()sizeof()**的区别
    • 首先严格来说,字符串 "0123" 在内存中实际占用 5 个字节,因为 C 语言中字符串常量会在末尾自动添加一个字符串结束符 '\0' ,即它在内存中存储为 '0''1''2''3''\0'
    • strlen() 函数的功能是计算字符串的有效字符长度,它从字符串的起始位置开始计数,遇到 '\0' 时停止计数,并且不计入 '\0' 本身 。所以 strlen("0123") 或对存储了 "0123" 的字符数组调用 strlen() 函数,输出结果是 4 ,代表的是字符串 "0123" 中有效字符的个数。
    • sizeof 运算符在计算字符数组(存储字符串)大小时,会把末尾的 '\0' 占用的字节算进去,比如 char s[] = "0123";sizeof(s) 的结果就是 5。

字符数组结尾特性

  • char str[]="Hello",*p;p=str;此时*(p+5)中的值为"\0",因为前五个元素为Hello,第六个元素自动赋值为"\0"

数组指针特性

  • 如上图所示,数组名也代表了数组首位元素的地址。

函数strcpy()

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <string.h>

int main() {
char s[] = "Beijing";
// 先使用strcpy将"China"复制到s中,再用strlen获取复制后字符串长度并输出
printf("%d\n", strlen(strcpy(s, "China")));
return 0;
}
  • strcpy(s, "China")strcpy 函数功能是把第二个参数(源字符串 "China" )拷贝到第一个参数 s 指向的字符串空间中,此函数会返回目标字符串的首地址,也就是 s 的首地址 。执行后,数组 s 中的内容从 "Beijing" 变为 "China"
  • strlen(strcpy(s, "China"))strlen 函数用于计算字符串长度,统计的是字符串中字符的个数(不包含字符串结束符 '\0' ) 。因为 strcpy(s, "China") 执行后 s 内容是 "China" ,它有 5 个字符,所以 strlen 函数返回值为 5 。
  • 最后 printf("%d\n", strlen(strcpy(s, "China"))); 输出的就是 strlen 函数的返回值 5 。

数组和指针的区分

  • 首先,对于字符数组名array和字符指针名strp,前者名字本身就指的是数组第一个元素的地址,后者则接收一个元素的地址。

  • 其次,前者为常量,不可以再赋值,而后者为变量,可以再进行赋值

  • 所以,一般对字符串型数组的赋值,都在初始化的时候完成,比如:char array[] = "hello world",不可以有:

  • char name[];
    name = "MMK";
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75

    * 因为这里的`name`代表了数组第一个字符型数据元素的地址,不可以直接用一个字符串数组对其赋值。

    * 要想之后改变数组的值,可以用指针遍历,也可以:借助`strcpy`(字符串复制)、`strncpy`(指定长度字符串复制)等函数来实现对字符数组的赋值。

    * 例题:

    * {% asset_img image-20250323172052232.png image-20250323172052232 %}

    * 这里,BCD选项很好排除,A选项,就是把`"Hello World"`首位地址给了字符指针`str`。

    ### 数组赋值

    * 直接看例题

    * {% asset_img image-20250325132846229.png image-20250325132846229 %}



    # 3.4

    ### 静态变量

    * 如下例题:

    * {% asset_img image-20250304074758892.png image-20250304074758892 %}

    # 3.5

    ### 宏

    #### 就是一个简单的替换

    * 我们可以看如下例题:
    * {% asset_img image-20250305133059085.png image-20250305133059085 %}
    * 由题可知:`F(5)`会被替换为:`F(x) 2.84+x`,在这道例题里面,`F(x)`被替换之后,是直接单纯被替换:会变成:`2.84+x*2`,不会自己多加什么运算顺序,不会变成:`(2.84+x)*2`。

    #### 基本特性

    宏具有以下特点:

    - 数据类型无限制:宏替换只是简单的文本替换,不涉及数据类型检查,可用于各种数据类型 。
    - 作用域灵活:通常写在函数外,作用域为其后的程序,但并非必须在所有语句之前 。
    - 命名自由:宏名习惯用大写,但不强制,小写或混合写法也可行。
    - 执行快速:宏在预处理阶段展开,不涉及函数调用时的诸如保护断点、现场恢复等操作,执行时间短 。
    - 代码复用与简化:能将重复代码片段或复杂操作封装,提高代码复用性与简洁度 。
    - 可实现条件编译:能根据条件决定部分代码是否参与编译 。

    注意:

    * 不一定需要在源程序的所有字母之前
    * 不一定需要大写字母表示
    * 宏调用不会比函数调用耗费时间。



    #### `EOF`:文件结束标志

    * {% asset_img image-20250323132436519.png image-20250323132436519 %}

    # 3.17

    ## 结构数据(持续更新)

    ### 数组赋值

    ```c
    #include<stdio.h>

    struct st{
    int n;
    struct st *next;
    }
    struct st a[3] = { 5,&a[0], 6,&a[1], 7,&a[2] }, *p;
    p = &a[0];

引用成员

  • 注释:
    • 在结构体的引用中,采用以下三种方式来引用结构体中的成员:
    • 结构体变量名.成员名
    • 指针变量名->成员名
    • **(指针变量名).成员名*

3.20

数值表示方法

不同进制数的表述方法

  • 二进制:0b0B

  • 八进制:0开头,如010

    • 注意题目里面的陷阱:strlen("\t\065");\065指的是8进制字符,算一位字符。
    • 我想说的就是,8进制数,也可以通过转义符\使用\0来表示
  • 十六进制:0x开头

    • 十六进制的数,可以通过转义符\使用\x进行表示。例如:\x23,即十六进制的23H

C题目基础概念

标识符

  • c语言中的标识符分为三大类:关键字、预定义标识符、用户标识符

  • 关于他们的详细解释:

  • 关键字:

  • 特点:具有明确且固定的功能和使用场景,是 C 语言语法规则的重要组成部分。

  • 举例:

    • 数据类型关键字int(表示整型数据 )、float(表示单精度浮点型数据 )、double(表示双精度浮点型数据 )、char(表示字符型数据 )等,用于声明不同类型的变量。
    • 流程控制关键字if(用于条件判断 )、else(与if 配合使用,构成条件分支结构 )、for(用于循环结构 )、while(用于循环结构 )、do(与while 配合构成do - while 循环结构 )、switch(用于多分支选择结构 )、caseswitch 结构中用于标识不同分支 )、break(用于跳出循环或switch 结构 )、continue(用于跳过本次循环中剩余语句,继续下一次循环 )等。
    • 存储类型关键字auto(自动存储类型 )、static(静态存储类型 )、register(寄存器存储类型 )、extern(声明外部变量或函数 )等,用于修饰变量或函数的存储特性。
  • 预定义标识符:

  • 定义:是 C 语言系统预先定义好的标识符,通常是库函数名、宏名等。虽然系统已经对它们有定义,但用户可以对其进行重新定义。不过重新定义后,其原有的系统定义功能可能会受到影响。

  • 特点:在标准库或系统相关功能中有特定用途,但在一定程度上允许用户重新赋予其含义,不过这样做可能导致程序的可读性和可维护性变差,甚至引发错误。

  • 举例:

    • 库函数名printf 是标准输出库函数,用于将格式化的数据输出到标准输出设备(通常是屏幕 );scanf 是标准输入库函数,用于从标准输入设备(通常是键盘 )读取数据;strlen 用于计算字符串的长度等。
    • 宏名:比如 NULL ,在标准库中被定义为表示空指针的值,在 <stdio.h> 等头文件中通常有相关定义(一般定义为 ((void*)0) ) 。
  • 自定义标识符

  • 定义:由用户根据实际编程需求,按照标识符的命名规则自行定义的名称,用于给变量、函数、结构体、枚举等命名。

  • 特点:灵活性高,但要遵循一定的命名规则,以保证程序的合法性和可读性。

  • 规则:

    • 由字母(大写和小写英文字母 )、数字和下划线组成。
    • 必须以字母或下划线开头,不能以数字开头。例如,myVariable_count 是合法的自定义标识符;而 2var 是不合法的,因为它以数字开头。
    • 不能与关键字同名,否则会引发语法错误。
    • 尽量做到 “见名知意” ,提高程序的可读性。比如用 studentAge 表示学生年龄,totalScore 表示总分数等。
  • 相关例题

程序的开始

C 程序必须有一个main函数,但不是由main语句开始 。main函数是程序的入口,程序从main函数的第一条语句开始执行。比如:

1
2
3
4
5
6
#include <stdio.h>

int main() {
printf("Hello, world!");
return 0;
}

是从main函数里的printf("Hello, world!"); 这条语句开始执行程序逻辑的,而不是说 C 程序由main语句开始,不存在 “main语句” 这种说法,所以该选项错误。

注释的嵌套

C 语言中传统注释方式/* */ 不允许嵌套 。因为/* 表示注释开始,*/ 表示注释结束,一旦嵌套,编译器就没办法正确识别注释的起止位置了。比如下面这样写是错误的:

1
2
3
4
5
/*
int num = 10;
/* 这是错误的嵌套注释 */
num++;
*/

不过在 C99 及之后标准引入的// 单行注释可以在/* */ 注释中使用 ,但这不属于严格意义上的注释嵌套,所以该选项错误。

关于代码块

  • 比如:

  • int fun(char *s)
    {
        char *t = s;
        while(*t++);
        t--;
        return(t-s);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38

    * 这里面,`while`循环后面直接以`;`分号结束,即为空语句,该`while(*t++)`的作用只有检查该语句是否为空。

    * 注意:停止符`\0`也算空。

    ### 杂题

    * 例题一
    * {% asset_img image-20250324155350716.png image-20250324155350716 %}



    # 3.23

    ## 占位符

    ### 格式控制符

    * 如`%2d`:

    * **在`printf`函数里的含义**

    `%2d`用于输出整数,其中的`2`代表最小字段宽度。也就是说,输出的整数至少会占据 2 个字符的宽度。若整数的位数不足 2 位,就会在左边补空格;若整数的位数超过 2 位,则按实际位数输出。

    以下是示例代码:

    ```c
    #include <stdio.h>

    int main() {
    int num1 = 5;
    int num2 = 123;

    printf("'%2d'\n", num1);
    printf("'%2d'\n", num2);

    return 0;
    }
    此代码的输出结果为:
    1
    2
    ' 5'
    '123'
    在输出`num1`时,因为`5`仅有 1 位,所以在左边补了一个空格,以此达到最小宽度 2;而`num2`有 3 位,超过了最小宽度 2,就按实际位数输出。 **在`scanf`函数里的含义** 在`scanf`函数中,`%2d`表示最多读取 2 位数字作为整数输入。 以下是示例代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <stdio.h>

    int main() {
    int num;
    printf("请输入一个整数:");
    scanf("%2d", &num);
    printf("你输入的整数是:%d\n", num);

    return 0;
    }
    若你输入`123`,`scanf`只会读取前 2 位数字`12`,并将其赋值给变量`num`。最终程序会输出:
    1
    你输入的整数是:12
    综上所述,`%2d`在输出时规定了最小字段宽度,在输入时限制了最多读取的数字位数。 ### 小数有关的格式控制符 #### 有关字符宽度限制详解:
  • 公式:%m.nf

  • 这里0.5表示小数后五位,

  • 5.0则表示只取整数的五位。

    函数

    有关函数形参

  • 当没有指定C语言中函数形参的储存类别时,函数形参的储存类别是自动

  • (extern)extern用于声明变量或函数的作用域为外部链接,即该变量或函数在其他源文件中定义,可在当前文件中使用。它不是函数形参默认的存储类别 。比如在一个多文件项目中,若在file2.c中定义了int num; ,在file1.c中想使用它,就用extern int num;声明。但函数形参不会默认是这种存储类别 。

  • (static)static修饰的变量具有静态存储期,在程序运行期间一直存在。局部变量被static修饰后,其生命周期延长到程序结束,且只初始化一次。函数形参不会默认是静态存储类别 ,因为若形参是静态的,每次函数调用时其值就不会像正常情况那样根据实参传递新值,而是保留上次调用结束时的值,这不符合函数形参常规语义 。

  • (register)register建议编译器将变量存储在寄存器中,以加快访问速度。但这只是一种请求,编译器不一定会采纳。而且函数形参默认不是这种存储类别 ,因为编译器会综合考虑寄存器资源等因素来决定是否将变量放入寄存器,不会默认将形参作为寄存器变量 。

  • (auto)auto表示自动存储类别,函数形参在没有指定存储类别的情况下,默认就是auto类型 。自动变量存储在栈区,在函数调用时为形参分配内存空间,函数调用结束时,该内存空间被释放。比如void func(int a) ,这里的a默认就是auto存储类别,在函数被调用时创建,函数执行完销毁。

文件

数据输出

常量

  • C 语言中有整型常量、实型常量、字符型常量、字符串常量等不同类型。

实型常量

字符和字符串

字符与字符串的表示

  • 字符:在 C 语言中用单引号' '表示,如'a' ,代表单个字符,存储的是对应 ASCII 码值,本质为整数,占用 1 个字节。
  • 字符串:用双引号" "表示 ,如"abc" ,是由 0 个或多个字符组成的序列,编译器自动在末尾添加'\0'作为结束标志,占用字节数为字符数加 1。

占位符

  • 字符占位符%c ,用于printf输出和scanf输入单个字符。
  • 字符串占位符%s ,用于printf输出和scanf输入以'\0'结尾的字符数组(即字符串 )。

例题

转义符

在 C 语言中,转义字符是以反斜杠 “\” 开头,后面跟一个或几个字符,用来表示一些特殊的控制字符、格式字符或有特殊用途的字符,以下是常见的 C 语言转义字符:

一般转义字符

转义字符 含义 示例及说明
\a 响铃符,会使终端发出警报声或出现闪烁(或两者同时发生) printf("\a");执行时,电脑会发出提示音
\b 退格符,将当前位置移到前一列 printf("abc\b"); 输出结果是abc被退格删除
\f 换页符,光标移到下一页(在现代系统上,行为类似\v ,效果不明显) printf("\f");
\n 换行符,将当前位置移到下一行的开头 printf("hello\nworld"); 输出时helloworld会分别在两行
\r 回车符,将当前位置移到本行的开头 printf("123\rabc"); 输出结果是abc123被覆盖
\t 水平制表符,使光标移到下一个水平制表位(通常是下一个 4/8 的倍数位置),类似于按下 Tab 键 printf("a\tb");ab之间会有一定间隔
\v 垂直制表符,使光标移到下一个垂直制表位,通常是下一行的同一列 printf("a\vb");
| 反斜线字符,用于表示一个反斜杠,因为反斜杠本身是转义字符的开头,所以需要用\\来表示反斜杠本身 printf("c:\\test"); 输出c:\test
单引号字符,用于表示字符常量中的单引号 char ch = '\''; 定义一个字符变量ch ,其值为单引号
双引号字符,用于表示字符串内部的双引号 printf("He said, \"Hello!\""); 输出He said, "Hello!"

八进制转义字符

形式为\dddddd表示 1 到 3 位八进制数,该转义字符表示的是对应八进制数所代表的 ASCII 码字符。例如,\101 表示字符A(因为八进制101转换为十进制是 65,65 是字符A的 ASCII 码值 )。

十六进制转义字符

形式为\xhhx后面跟 1 到 2 位十六进制数,同样表示对应十六进制数所代表的 ASCII 码字符。例如,\x41 也表示字符A(十六进制41转换为十进制是 65 )。

ASCII码问题

  • 比如将数字字符转换为对应的数字:

  • 代码:

  • /*略过*/
    sum += *s - '0';
    
  • 字符'0''9'在 ASCII 码表中是连续编码的 。例如字符'0'的 ASCII 码值是 48 ,字符'1'的 ASCII 码值是 49,以此类推。所以如果想把字符形式的数字(如'5' )转换为数值 5,就需要减去字符'0'的 ASCII 码值(即48)。

    代码sum += *s - '0'; 中,*s是获取当前指向的字符,减去'0' (字符'0'的字符形式 ),就能得到这个字符对应的数值,然后累加到sum中,实现将字符串中的数字字符转换为数值并累加的功能。