Linux下C语言教程-李慧芹老师-第二章

配套代码笔记Github地址

目录

第二章 数据类型,运算符和表达式

数据类型(基本数据类型)

数据类型所占字节数随机器硬件不同而不同,以int为基准,char比它小,floatdouble比它大。

  1. 所占字节数

  2. 存储区别

  3. 不同类型的数据间转换(隐式和显式=>强制类型转换)

  4. 特殊性

    1. 布尔型(bool):

      false0true:非零值。

    2. float类型,不够精确,其实是一个小范围。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      int func(float f)
      {
      if (f < 0)
      return -1;
      // else if (f == 0)
      else if (fabs(f - 0) <= 1e-6)
      return 0;
      else
      return 1;
      }
    3. char型是否有符号,未定义

    4. 不同形式的零值

      0'0'"0"'\0'NULL

    5. 数据类型得与后续代码的输入输入相匹配(自相矛盾)

常量与变量

常量

定义

在程序执行过程中值不会发生变化的量。

分类

  • 整型常量:1,790,76

  • 实型常量:3.14,5.26

  • 字符常量:由单引号引起来的单个字符或转义字符,如'a''X''\n''\ddd'(d可以是0-7,三位八进制,例如'\015'),'\xhh'(h可以是0-F,二位十六进制,'\x7f'

  • 字符串常量:由双引号引起来的一个或多个字符组成的序列(特殊情况,空串),如:"""a""abXYZ""abc\n\021\018"(注意这个字符串,\n\021都是算单个字符,\018又不算)

我问gpt,”\018”在C语言几个字符。
在C语言中,字符串是以字符数组的形式表示,而每个字符串的末尾都有一个特殊的字符'\0'(空字符),用于标识字符串的结束。当你提到的"\018"在C语言中出现时,它表示一个包含特殊字符的字符串字面量。

具体来说,\0在C语言中是一个转义字符,用于表示字符串的结束符(null terminator)。该符号用于标记字符串的结束,确保字符串处理函数如printfstrcpy等知道在哪里停止处理。

"\018"中,\0表示字符串的结束,后面跟的18并不会作为字符串的一部分来处理。因此,如果你在C语言中使用"\018",它实际上会被视为只包含一个空字符(\0)的字符串。这意味着,尽管看起来字符串中有更多的字符,但由于\0的存在,它实际上在C语言处理中表示一个空字符串。

总结来说,在C语言中"\018"实际上会被看作一个包含单一结束字符(null terminator)的字符串,而不是包含多个字符。这种处理方式是因为C语言使用空字符来标记字符串的结束。

  • 标识常量:#define

    特点:完整的用宏体替换宏名,不做任何检查

    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
    #include <stdio.h>
    #include <stdlib.h>

    #define PI 3.14
    #define ADD 2 + 3
    #define ADD1 (2 + 3)

    #define MAX(a, b) (a > b ? a : b)
    #define MAX1(a, b) ((a) > (b) ? (a) : (b))

    int main()
    {
    // printf("%d\n", ADD * ADD);
    // 相当于 2+3*2+3
    // 故输出11而不是25
    // printf("%d\n", ADD1 * ADD1);
    // 相当于(2+3)*(2+3)
    // 正常输出25

    int i = 5, j = 3;
    // printf("%d\n", MAX(i, j));
    // 输出5

    // printf("%d\n", MAX(i, j * 2));
    // 输出6

    printf("i=%d\tj=%d\n", i, j);
    printf("%d\n", MAX1(i++, j++));
    printf("i=%d\tj=%d\n", i, j);
    // 输出:
    // i = 5 j = 3
    // 6
    // i = 7 j = 4
    // 为什么i自增了两次?
    // 预处理结果
    // printf("%d\n", ((i++) + (j++) ? (i++) : (j++)));

    exit(0);
    }

    解决办法:

    1. 使用函数:函数与宏的区别在于,一个占用编译时间,一个占用运行时间。在linux内核中多用宏。
    2. 在宏中进行变量保存,这种写法超出标准C,属于GNU C的扩展部分,只能在支持的编译器(如gcc)中使用,在linux内核中非常常用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #define MAX2(a, b)               \
    ({ \
    int A = a, B = b; \
    ((A) > (B) ? (A) : (B)); \
    })

    #define MAX3(a, b) \
    ({ \
    typeof(a) A = a, B = b; \
    ((A) > (B) ? (A) : (B)); \
    })

变量

用来保存一些特定内容,并且在程序执行过程中值随时会发生变化的量。

定义

[存储类型] 数据类型 标识符 =
TYPE NAME = VALUE
  • 标识符:由字母,数字,下划线组成且不能以数字开头的一个标识序列。拟定时尽量做到见名知义。
  • 数据类型:基本数据类型、构造类型
  • 值:注意匹配
  • 存储类型:autostaticregisterextern(说明型)
    • auto:默认,自动分配空间,自动回收空间。
    • register:(建议型,编译器不一定采用)寄存器类型,只能定义局部变量,不能定义全局变量;大小有定义,只能定义32位大小的数据类型,如double就不可以;集尘器没有地址,所以一个寄存器类型的变量无法打印出地址查看或使用。
    • static:静态型,自动初始化为0值或空值,并且变量的值有继承性。另外,常用来修饰一个变量和函数,防止其对外扩散。
    • extern:说明型,意味着不能改变被说明的变量的值或类型。
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
#include <stdio.h>
#include <stdlib.h>

#if 0
void func(void)
{
int x = 0;
x = x + 1;
printf("%p->%d\n", &x, x);
}

void func1(void)
{
static int x = 0;
x = x + 1;
printf("%p->%d\n", &x, x);
}

int main()
{

// auto int i;
// printf("i=%d\n", i);
// out:i=21915,每次都不一样

// static int i;
// printf("i=%d\n", i);
// out:i=1

// func();
// func();
// func();
// out::
// 0x7ffc3c4ca4f4->1
// 0x7ffc3c4ca4f4->1
// 0x7ffc3c4ca4f4->1
// 三次地址看着一样
// 但是是每次函数开始取用,结束销毁的
// 只是gcc刚好都取的栈上同一块地址

func1();
func1();
func1();
// out::
// 0x55fd83c96014->1
// 0x55fd83c96014->2
// 0x55fd83c96014->3

exit(0);
}
#endif

#if 0
int i = 100;

void func(int i)
{
printf("i=%d\n", i);
}

int main()
{

int i = 3;

// printf("i=%d\n", i);
// out:
// i=3

// {
// printf("i=%d\n", i);
// }
// out:
// i=3

// {
// i = 5;
// printf("i=%d\n", i);
// }
// out:
// i=5

// func(i);

exit(0);
}
#endif

int i = 0;

void print_star(void)
{
for (i = 0; i < 5; i++)
printf("*");
printf("\n");
printf("[%s]i=%d\n", __FUNCTION__, i);
}

int main()
{
for (i = 0; i < 5; i++)
print_star();
printf("\n");

// out:
// *****
// [print_star] i = 5

exit(0);
}

变量的生命周期与作用范围

  1. 全局变量和局部变量
  2. 局部变量和局部变量
  3. 参考图片存储类型比较

存储类型比较

这一块具体讲解见代码仓库/Chapter2/变量/

minproj例子中,如果在proj.cproj.hstatic定义func函数,而在main.c中调用func

1
2
3
4
5
6
7
8
[main][~/LinuxC/Chapter2/Section5/minproj]$ gcc *.c
In file included from main.c:4:
proj.h:4:13: warning: ‘func’ used but never defined
4 | static void func(void);
| ^~~~
/usr/bin/ld: /tmp/ccQ2317U.o: in function `main':
main.c:(.text+0x2f): undefined reference to `func'
collect2: error: ld returned 1 exit status

运算符和表达式

表达式和语句的区别

  • 运算符部分

    运算符

    • 每个运算符所需要的参与运算的操作数个数

    • 结合性

    • 优先级

    • 运算符的特殊用法

      如:%(要求左右两边都是整形),===,逻辑运算(&&||)的短路特性

    • 位运算的重要性

      << >> ~ | ^ &

      1. 将操作数中第n位置1,其他位不变:num = num | 1 << n;

      2. 将操作数中第n位清0,其他位不变:num = num & ~(1 << n);

      3. 测试第n位:if(num & 1 << n)

      4. 从一个指定宽度的数中取出某几位:

        1
        2
        3
        4
        5
        // 假设取一个32位整数的第10位到第15位
        unsigned int number;
        unsigned int mask = ((1 << 6) - 1) << 9; // 6 是位数(15-10+1),9 是起始位置(10-1)
        unsigned int result = number & mask;
        result = result >> 9;

运算符相关代码

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
#if 0
i++相当于i=i+1
i--相当于i=i-1

int i=1;
i++;表达式值为1,i值为2
++i;表达式值为2,i值为2

#endif

#include <stdio.h>
#include <stdlib.h>

#if 0
int main()
{
int i = 1, j = 10, value;

// value = i++ + ++j;
// 相当于
// j = j + 1;
// value = i + j;
// i = i + 1;
// out:
// i = 0 j = 11 value = 10

// value = --i + j++;
// i = i - 1;
// value = i + j;
// j = j + 1;
// out:
// i = 1 j = 12 value = 12

// value = i++ + ++i - i-- + --i;
// 避免单个变量多次自增或者自减
// 不同编译器可能结果不同,也难为自己和他人

// printf("i=%d\n", i);
// printf("j=%d\n", j);
// printf("value=%d\n", value);

// printf("%d\n", i > j);

int a = 1, b = 2, c = 3, d = 4;
int m = 1, n = 1;

// (m = a > b) && (n = c > d);
// printf("m = %d\nn = %d\n", m, n);
// a>b为假,所以左边为0,右边直接不判断了,n依旧为1而不是0!
// out:
// m = 0
// n = 1

(m = a > b) || (n = c > d);
printf("m = %d\nn = %d\n", m, n);
// a>b为假,所以左边为0,右边继续判断
// out:
// m = 0
// n = 0

exit(0);
}
#endif

#if 0
int main()
{
int i = 0, j = 10, value;

// int a = 6;
// a -= a *= a += 3;
// a -=.. 81-81=0
// a *=.. 9*9=81给上面
// a += 3; 6+3=9给上面
// 故a=0

// printf("%d\n", sizeof(int));
// printf("%d\n", sizeof(double));
// out:
// 4
// 8

// int a = 3;
// float f = 3.9;
// a = f;
// printf("a=%d\n", a);
// printf("f=%f\n", f);
// out:
// a=3
// f=3.900000

// int a = 3;
// float f = 3.9;
// a = (int)f; // 这个过程不改变f本身的地址和值
// printf("a=%d\n", a);
// printf("f=%f\n", f);
// out:
// a=3
// f=3.900000

exit(0);
}
#endif


Linux下C语言教程-李慧芹老师-第二章
http://sinlatansen.github.io/2024/03/12/Linux下C语言教程-李慧芹老师-第二章/
作者
fugu
发布于
2024年3月12日
许可协议