C

C

C programming

大学学过c语言的应该非常多,但是能够领悟c的精髓的太少了,现在应该是适合再次看c的时候了,也正好 为看内核做更好的补充,因为我需要写例子来佐证我对内核的理解程度,不能停留纸上功夫。

这里推荐大家实践的一个好办法是去leecode里面找些题来做,只有痛过,才会印象更深。

介绍

c语言是静态语言,需要编译才能运行

c语言的程序可以很方便的移植到其他体系结构的服务器,比如arm,darwin等

c的语法相对比较简单,没有复杂的语法,没有类

c语言没有gc,内存需要自己管理

基本语法

hello, world!

注意emacs里面begin_src C,org-babel-load-languages里面要有C . t

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

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

一个c程序需要包含一个main函数,作为入口

printf

printf和golang里面的fmt.Printf基本一样

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

  int main() {
    printf("Hello %s.\nSay 1 2 %i", "liuliancao", 3);
  }
Hello liuliancao.
Say 1 2 3
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
printf的占位符种类
- %a, 浮点数float
- %c, 字符char
- %d, 十进制整数decimal
- %e, 用科学计数法,指数部分为e
- %i,整数int
- %o, 八进制整数octet
- %p,指针pointer
- %s,字符串string
- %%, 输出一个%

变量

命名
  • 字母、数字和下划线组成
  • 不能以数字开头
  • 长度不建议超过63个字符
1
2
3
4
5
6
7
8
  #include <stdio.h>

  int main() {
    //_123 = 0;
    int liuliancaoliuliancaoliuliancaoliuliancaoliuliancaoliuliancaoliuliancao = 0;
    printf("liuliancao is %d", liuliancaoliuliancaoliuliancaoliuliancaoliuliancaoliuliancaoliuliancao);
    return 0;
  }
liuliancao is 0
使用
1
2
3
4
5
6
7
8
9
  #include <stdio.h>

  int main() {
    int a;
    a = 1;
    int b = 2;
    printf("liuliancao is in %i room.\nbenben is in %i room.",a,b);
    return 0;
  }
liuliancao is in 1 room.
benben is in 2 room.
运算符
算数运算符

+,-,*,/,%

关系运算符

>,<,==,!=,>=,<=

逻辑运算符

!,&&,||

位运算符
~ 取反
1
2
3
4
5
6
  #include <stdio.h>
  int main() {
    for(int i=0;i<11;i++){
      printf("~%d is %d\n",i,~i);
    }
  }
~0 is -1
~1 is -2
~2 is -3
~3 is -4
~4 is -5
~5 is -6
~6 is -7
~7 is -8
~8 is -9
~9 is -10
~10 is -11

假设为8位,你可能会觉得比较奇怪,上面的计算

这个和计算机存储二进制方式有关,计算机是存储二进制的补码

正数的补码和源码一致,比如1,

原码:0000 0001 补码:0000 0001

负数的补码符号位不变,原码取反再1, 比如-1

原码:原码1000 0001 补码:符号位不变,取反1111 1110 +1 1111 1111

所以0在计算机里面对应0000 0000,取反是1111 1111,这个补码对应的值是-1

2在计算机对应的0000 0010,它的补码是0000 0010,取反1111 1101获得计算机存储的补码

补码转原码,和原码到补码相反,符号位不变 -1取反 -1 1111 1100 取反1000 0011,所以2的取反在计算机中是-3

-2也算下 二进制:1000 0010 符号不变取反:1111 1101 +1:1111 1110 谁的补码是这个呢 -2取反是0000 0001,这个是1的补码,所以~-2=1

你可能发现了~n=-(n+1)

所以记得,计算机里面取反和二进制取反不同在于不是以反码形式存储的,这个我觉得是为了 表示不同的1000 0000和0000 0000,以补码这种形式可以利用每一个进制且不会有歧义

& 与
1
2
3
4
5
6
7
8
  #include <stdio.h>

  int main() {
    int a = 1 & 2;
    int b = 1 & -3;
    printf("1 & 2 is %d\n", a);
    printf("1 & -3 is %d", b);
  }
1 & 2 is 0
1 & -3 is 1

仍然以8位为例 eg1: 0000 0001 补码0000 0001 0000 0010 补码0000 0010 0000 0000 补码0000 0000

eg2: 0000 0001 补码0000 0001 1000 0011 补码1111 1101 0000 0001 补码0000 0001

和取反一样,我觉得&也应该是取补码进行相与,随便弄了两个例子结果是一样的

| 或

1 | 1 = 1, 1 | 0 = 1, 0 | 0 = 0, 0 | 1 = 1

^ 异或

1 | 0 = 1, 1 | 1 = 0, 0 | 1 = 1, 0 | 0 = 0

<<左移运算符, 右移 >>

运算数每一位左移2位,替换成0

1
2
3
4
5
6
7
8
  #include <stdio.h>
  int main() {
    int a = 8 << 2;
    printf("8 << 2 is %d\n", a);
    int b = 8 >> 2;
    printf("8 >> 2 is %d", b);
    return 0;
  }
8 << 2 is 32
8 >> 2 is 2

按8位 8二进制0000 1000,左移2位0010 0000,为32 右移2位0000 0010,为2

逗号运算符
1
2
3
4
5
6
7
8
9
  #include <stdio.h>

  int main() {
    int a = 1, b = 2;
    int c;
    c = 1, 2, 3;
    printf("a is %d, b is %d\n", a, b);
    printf("c is %d", c);
  }
a is 1, b is 2
c is 1

,连接每个表达式 从左到右依次执行

优先级

优先级顺序

1
2
3
4
5
6
7
  1. 圆括号()
  2. 自增运算符++,自减运算符--
  3. 一元运算符+,-
  4. 乘*,除/
  5. 加+,减-
  6. 关系运算符<, >等
  7. 赋值运算符=

一般来说,就我个人来说不建议写出混淆的代码,写出的代码需要可读性,而不是产生意想不到的结果哈。

流程控制

一般来说,代码是继续执行还是调到哪里,还是循环,这里相当于workflow如何设计。

if

if (条件) statement

if大家都会的哈,只是建议不要多层套用(想想全是if的代码),另外建议优先写likely的 少写if的方法有很多种,比如:

通过其他方法简化写法

  • 比如switch或者三元表达式
  • 放进数组里面,通过下标方式访问

通过重构

  • 看是不是完全不可能发生的事情,是不是没必要的if,而尾端,通常可以少写一个else
  • 使用面向对象的方法,这里不细说了哈
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  #include <stdio.h>

  int main() {
    int a = 59;
    if (a >= 60) {
      printf("a is not bad at scores: %d", a);
    } else {
      printf("a is not ready at scores: %d", a);
    }
    return 0;
  }
a is not ready at scores: 59
switch(开关)

switch-case这种很多语言都有,存在即合理,可以增加条件的可读性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  #include <stdio.h>
  int main() {
    int a = 59;
    switch(a) {
      case 60: printf("liuliancao is good at score: %d\n", a);break;
      case 59: printf("liuliancao needs to be harder at score: %d\n", a);break;
      default: printf("liuliancao is a mystery.\n");
    }
    return 0;
  }
liuliancao needs to be harder at score: 59

break一定要记得加哈

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  #include <stdio.h>
  int main() {
    int a = 59;
    switch(a) {
      case 60: printf("liuliancao is good at score: %d\n", a);
      case 59: printf("liuliancao needs to be harder at score: %d\n", a);
      default: printf("liuliancao is a mystery.\n");
    }
    return 0;
  }
liuliancao needs to be harder at score: 59
liuliancao is a mystery.
三目运算符(ternary operator)

expression ? express真的时候 : expression为假的时候

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

  int main() {
    int a = 59, b = 60;
    int max = a > b ? a : b;
    printf("max is %d", max);
    return 0;
  }
max is 60
while(只要)

while(只要条件满足){我就执行;}

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

  int main() {

    int a = 10;
    while(a >= 0) {
      printf("a is now: %d\n", a);
      a--;
    }
    return 0;
  }
a is now: 10
a is now: 9
a is now: 8
a is now: 7
a is now: 6
a is now: 5
a is now: 4
a is now: 3
a is now: 2
a is now: 1
a is now: 0
do while(先做一下,再说条件)

do while和while不一样,do while是先做do里面的语句,然后再判断是否再do

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

  int main() {
    int a = 10;
    do {
      printf("a is now: %d\n", a);
      a--;
    } while (a >= 0);
    return 0;
  }
a is now: 10
a is now: 9
a is now: 8
a is now: 7
a is now: 6
a is now: 5
a is now: 4
a is now: 3
a is now: 2
a is now: 1
a is now: 0

可以发现,其实和while效果一样,只是这个至少会做一次,比如我们执行到这里了,我们希望至少有日志 输出,那么我们可以这样写, 这样机器状态好的时候我们也能看到一行日志 do { log recording… } while (state is bad)

for循环

永远的classical for loop

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

  int main(){
    for(int i=0;i<10;i++){
      printf("%d\n",i);
    }
    return 0;
  }
0
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
  #include <stdio.h>

  int main() {
    for(int i=0,j=10;i<10;i++,j--){
      printf("i: %d, j: %d\n", i, j);
    }
    return 0;
  }
i: 0 j: 10
i: 1 j: 9
i: 2 j: 8
i: 3 j: 7
i: 4 j: 6
i: 5 j: 5
i: 6 j: 4
i: 7 j: 3
i: 8 j: 2
i: 9 j: 1

i从0到9,十次循环,同样作用于j,10到1

数据类型

C语言是静态语言,和python不一样,能通过变量具体去猜 每一个变量都需要定义明确的数据类型,这样编译器才能通过,为之分配最合适的编译参数

基本数据类型分为字符(char), 整数(int), 浮点数(float)

字符(char)

字符类型char指的是单个字符,由一个字节(位)存储,字符变量放在单引号里面,每个字符和ascii码是有 对应关系的,所以也可以赋值对应的ascii码, 除了ab…AB…还有,啥的都是字符

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

  int main() {
    char a = 'a';
    char b = 'b';
    char A = 'A';
    char B = 'B';
    char dou = ',';
    printf("a is %d,\n", a);
    printf("b is %d,\n", b);
    printf("A is %d,\n", A);
    printf("B is %d,\n", B);
    printf(", is %d,\n", dou);
    return 0;
  }
a is 97
b is 98
A is 65
B is 66
is 44

大写字母位于65-96 小写字母97-128

整数(int)

整数类型是int,无符号和有符号在前面分别加unsigned和signed, int默认是有符号的

整数int也可以分开为short int, long int, long long int, 那么如何获取这些的最大最小值呢

以int为例, 其实我们可以反推下 我们只是位数不知道,对于无符号的值,最大是什么呢 这个最大值对应的是全1, 全1是原码,反求补码也是全1,这个是无符号0的取反, 直接对0取反就是unsigned int的最大值

如果是有符号位,最大值是011,对应的补码是011我们无从下手 最小值是111,计算机里面对应的补码是(001) 而这个值是1的补码,也就是~1 = 这个值 max real -> max buma 01..1 -> 01..1

补码取反10..0 是-0,这个无法拿到对应的y y real -> y buma

min real -> min buma 11..1 -> 10..1 补码取反找到那个数的补码值01..0 不好找,哈哈

这里只需要注意取反是对补码(计算机的存储方式)的取反就好了,无符号0取反就是无符号最大值,记住 这点

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

  int main() {
    unsigned int a = 0;
    unsigned int max_u = ~a;
    unsigned int min_u = 0;
    signed int max_s = max_u / 2;
    signed int min_s = -max_s - 1;
    printf("u: max: %u, min: %u\n", max_u, min_u);
    printf("s: max: %d, min: %d\n", max_s, min_s);
    return 0;
  }
u: max: 4294967295 min: 0
s: max: 2147483647 min: -2147483648

在标准库limits.h里面已经定义了int max和int min等临界,需要的时候建议调用这个

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

  int main(){
    printf("s: max: %d, min: %d", INT_MAX, INT_MIN);
    return 0;
  }
s: max: 2147483647 min: -2147483648
浮点数(float)

使用float关键字

占用4个字节,8位存放指数和符号,24位存放小数和符号

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

  int main() {
    float a = 1e3;
    printf("a is %f", a);
  }
a is 1000.000000
布尔(bool)

c中没有bool,一般用0,1表示,如果需要,需要引入stdbool.h

占了多少字节sizeof
1
2
3
4
5
6
7
  #include <stdio.h>

  int main() {
    printf("int is %d bytes.\n", sizeof(int));
    printf("float is %d bytes.\n", sizeof(float));
    return 0;
  }
int is 4 bytes.
float is 4 bytes.
指针pointer

指针,大家大学应该都学过,存的是内存地址。

其中int \*intPtr表示一个指向整数的指针,来看个例子

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

  int main() {
    int *p = NULL;
    int a = 3;
    p = &a;
    printf("variable a %d, is in %p which is %d\n", a, p, *p);
    return 0;
  }
variable a 3 is in 0x7ffdcc6ee404 which is 3

所以,

  • 指针也是一个变量
  • 变量里面存的是内存地址
  • 取变量地址用&操作符
  • 取指针指向用*操作符
指针取值与指针例子,取最大值
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  #include <stdio.h>

  int max(int *a, int *b) {
    return *a > *b ? *a : *b;
  }
  int main() {
    // definite pointers
    int *p1 = NULL;
    int *p2 = NULL;
    int c = 5, d = 6;
    // add pointer
    p1 = &c;
    p2 = &d;
    printf("c %d, d %d max is %d",*p1, *p2, max(p1, p2));
  }
c 5 d 6 max is 6
函数function

函数是一组可以重复执行的代码,

1
2
3
4
返回类型 函数名(param1, param2) {
    statement1;
    statement2;
}

返回类型如果不返回可以写void,参数如果是空参数,也可以写void

main()函数每个程序都必须有,程序从这里开始。

函数使用我就不介绍了,大家应该都会,简单看下使用递归方法来计算斐波那契数列

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

  int fb(int n) {
    if(n > 2) {
      return fb(n-1) + fb(n-2);
    } else {
      return 1;
    }
  }

  int main() {
    for(int i=1;i<10;i++) {
      printf("fb(%d) is %d\n", i, fb(i));
    }
    return 0;
  }
fb(1) is 1
fb(2) is 1
fb(3) is 2
fb(4) is 3
fb(5) is 5
fb(6) is 8
fb(7) is 13
fb(8) is 21
fb(9) is 34
函数参数
值传递

看一下交换两个数的操作哈,这个是菜鸟上面的例子, 大家尽量把例子自己默写下来

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

  void swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
  }

  int main() {
    int i=1, j=2;
    printf("i: %d, j: %d\n", i, j);
    swap(i, j);
    printf("i: %d, j: %d\n", i, j);
    return 0;
  }
i: 1 j: 2
i: 1 j: 2
指针传递
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
  #include <stdio.h>

  void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
    return;
  }

  int main() {
    int i=1, j=2;
    printf("i: %d, j: %d\n", i, j);
    swap(&i, &j);
    printf("i: %d,j: %d\n", i,j);
    return 0;
  }
i: 1 j: 2
i: 2 j: 1

为什么会改变呢?

前面我们说到变量其实是个符号,对应的是一个值,这个值放在内存里面,我们通过指针,修改到了对应内存的值

当我们再次访问这个变量的时候,访问的就是这个值了

需要注意下这里的例子,我们的函数主要目的是为了交换i和j的值,那比较好奇如何交换两个的地址呢?

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

  void swaptr(int *a, int *b) {
    printf("now addresses: a: %p, b:%p\n", a, b);
    int *temp = NULL;
    temp = a;
    a = b;
    b = temp;
    printf("swapped addresses: a: %p, b:%p\n", a, b);
    return;
  }

  int main() {
    int i=1, j=2;
    int *p1 = &i;
    int *p2 = &j;
    swaptr(p1, p2);
    printf("p1: %p, *p1: %d, p2: %p, *p2: %d\n", p1, *p1, p2, *p2);
    return 0;
  }
now addresses: a: 0x7fff4528177c b:0x7fff45281778
swapped addresses: a: 0x7fff45281778 b:0x7fff4528177c
p1: 0x7fff4528177c *p1: 1 p2: 0x7fff45281778 *p2: 2

为什么没改变呢, 因为我们虽然交换了地址,但是这个是在另一个函数空间的,有点类似回到值传递的过程, 指针地址被当成值进行传递,所以回过来还是不会执行,那怎么样才能修改才能让两个指针指向变化呢? 下面的例子

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

  void swaptr2(int **a, int **b) {
    int temp = **a;
    **a = **b;
    **b = temp;
    return;

  }
  void swaptr(int *a, int *b) {
    swaptr2(&a, &b);
    return;
  }

  int main() {
    int i=1, j=2;
    int *p1 = &i;
    int *p2 = &j;
    swaptr(p1, p2);
    printf("p1: %p, *p1: %d, p2: %p, *p2: %d\n", p1, *p1, p2, *p2);
    return 0;
  }
p1: 0x7ffd060f2b4c *p1: 2 p2: 0x7ffd060f2b48 *p2: 1

说白了,如果我们要改一个值,那么我们要找到这个值得地址,对地址进行修改,所以要改整数,那就要改 对应的整数指针,要改整数指针,那就要改二级指针,依次类推才可以。

extern

extern用于表示通过其他文件去找定义和变量

static

static可以定义全局静态变量,局部静态变量,定义静态函数

全局静态变量

全局静态变量有以下特点,

  • 在全局分配内存
  • 如果没有初始化,默认值为0
  • 该变量,在文件内定义开始到文件结束可见
局部静态变量

局部静态变量有如下特点,

  • 在全局分配内存
  • 始终驻留在全局数据区,直到程序运行结束
  • 作用于为局部作用域,当定义它的函数或者语句块结束,就结束

看下下面这个经典的例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  #include <stdio.h>

  static int n = 1;
  void selfadd(void) {
    int n = 0;
    n++;
    printf("add: n is %d\n", n);
  }
  void selfadd_static(void) {
    static int n = 0;
    n++;
    printf("add_static: n is %d\n", n);
  }

  int main() {
    selfadd();
    selfadd();

    selfadd_static();
    selfadd_static();

    printf("n is %d\n", n);
    return 0;
  }
add: n is 1
add: n is 1
add_static: n is 1
add_static: n is 2
n is 1

会发现,加static的时候让变量有了暂存功能

静态函数

静态函数有如下特点,

  • 静态函数只能在本源文件使用
  • 在文件作用域中声明的inline函数默认为static类型
  • 静态函数的好处在于其他文件无法引用,对于容易命名冲突的,比较方便,这一点内核代码里面会发现,

大量的inline函数

  • 由于静态函数的特点,所以可以用来当计数器
inline

内联函数,inline关键字,原来是函数可能在某个地方,现在相当于要执行的函数直接在附近,所以解决了效率问题 内联函数有如下特点,

  • 一般不要太多行,不能递归,不能使用循环
  • 函数声明处添加关键字会导致inline无效,inline需要放到函数定义处
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  #include <stdio.h>

  int add(int a);//这里不加inline, 注意不能加{}
  inline int add(int a) {
    return a + 1;
  }

  int main(){
    int b = add(1);
    printf("b is %d\n", b);
    return 0;
  }
b is 2
const

const表示常量,和static一样,通常表示常量,并且不可修改,所以 const可以防止环境变量被修改

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

  const int NAME = 5;


  int main() {
    printf("hello, %d\n", NAME);
    /* NAME = 3; cannot assign for readonly NAME */
    int b = stable();
    b = 3;
    printf("b is %d\n", b);
  }
hello, 5
b is 3
可变参数

可变函数通常用…表示,需要引入stdarg.h,

我个人理解还是少使用比较好,偏向单一原则

数组array

C里面的数组,是存一组相同类型的值,按照内存地址顺序,依次存放每一个值,从0开始编号,依次index的基础上+1个单位 来索引所有的数据

初始化

数组初始化的方式,通过{},请看下面一组例子

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

  int main() {
    int a1[5];
    a1[1] = 3;
    for(int i=0;i<sizeof(a1)/sizeof(int);i++) {
      printf("a1[%d] is %d\n",i, a1[i]);
    }
    int a2[5]={0};
    a2[1] = 3;
    for(int i=0;i<sizeof(a2)/sizeof(int);i++) {
      printf("a2[%d] is %d\n",i, a2[i]);
    }
    int a3[5]={0,1,2,3,4};
    a3[3] = 2;
    /* a3 = {1,2,3,4,5}; only can be used when decalaration */
    for(int i=0;i<sizeof(a3)/sizeof(int);i++) {
      printf("a3[%d] is %d\n",i, *(a3+i));
    }
    return 0;
  }
a1[0] is 129835584
a1[1] is 3
a1[2] is 129835088
a1[3] is 22025
a1[4] is 390302608
a2[0] is 0
a2[1] is 3
a2[2] is 0
a2[3] is 0
a2[4] is 0
a3[0] is 0
a3[1] is 1
a3[2] is 2
a3[3] is 2
a3[4] is 4

我们可以发现,

1
2
3
4
- 大括号只能在声明时候使用,再次使用会报错
- 通过sizeof(array)/sizeof(arraytype)可以获取到array的长度
- 数组最好设置默认值,否则可能出现比较奇怪的
- 由于数组的存取方式导致a = &a[0] = *(a+0)
多维数组

数组可以是多维的,比如[ [1,2 ], [3,4]], 类似这种就是一个二维数组,通过两层下标来标记

分别是

a[0][0] = 1, a[0][1] = 2,

a[1][0] = 3, a[1][1] = 4

看下小例子

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

  int main() {
    int a[2][2] = {{1,2},{3,4}};
    for(int i=0;i<2;i++) {
      for(int j=0;j<2;j++) {
        printf("a[%d][%d]: %d\n", i, j, a[i][j]);
      }
    }
    return 0;
  }
a[0][0]: 1
a[0][1]: 2
a[1][0]: 3
a[1][1]: 4
数组作为参数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
  #include <stdio.h>

  void print_array1(int array[], int len) {
    for(int i=0;i<len;i++) {
      printf("array[%d]: %d\n",i,array[i]);
    }
  }
  void print_array2(int* array, int len) {
    for(int i=0;i<len;i++) {
      printf("array[%d]: %d\n", i, *(array+i));
    }
  }
  int main() {
    int array[5] = {1,2,3,4,5};
    print_array1(array, 5);
    print_array2(array, 5);
    return 0;
  }
array[0]: 1
array[1]: 2
array[2]: 3
array[3]: 4
array[4]: 5
array[0]: 1
array[1]: 2
array[2]: 3
array[3]: 4
array[4]: 5

可以通过传头指针的方式

或者可以通过索引的方式

拷贝数组

由于数组的名字本质上是头指针,所以不能简单=,这会导致指向同一个数组,

所以真正的拷贝可以新建一个数组,逐个拷贝或者使用memcopy库函数拷贝

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

  int main() {
    int a[5] = {1,2,3,4,5};

    int a_copy[5] = {0};
    int b_copy[5] = {0};
    for(int i=0;i<5;i++) {
      a_copy[i] = a[i];
    }
    memcpy(b_copy, a, sizeof(a));
    for(int i=0;i<5;i++) {
      printf("a_copy[%d]: %d, b_copy[%d]: %d\n", i, a_copy[i],i,b_copy[i]);
    }
    return 0;
  }
a_copy[0]: 1 b_copy[0]: 1
a_copy[1]: 2 b_copy[1]: 2
a_copy[2]: 3 b_copy[2]: 3
a_copy[3]: 4 b_copy[3]: 4
a_copy[4]: 5 b_copy[4]: 5
字符串string

C里面没有字符串这个类型,而是通过char数组来表示字符串,通过尾巴放acii \0来表示字符串到了结尾

Hello表示为{'H','e','l','l','o','\0'}

字符串的定义
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  /* -*- coding: utf-8 -*- */
    #include <stdio.h>

    int main(){
      char s1[] = "Hello";
      char s2[11] = {'L','i','u','l','i','a','n','c','a','o','\0'};

      printf("%s,%s!",s1, s2);
      return 0;
    }
Hello Liuliancao!
字符串的函数

注意str后面都是三个字符串

示例程序

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

  int main() {
    char s1[7] = "hello "; // not s1[6], will show not recognized char
    char s2[11] = "liuliancao";

    char s3[10] = {""};
    strcpy(s3, s1);
    printf("strcopy(s3,s1) s3 is %s\n", s3);

    strcat(s3, s2);
    printf("s1 before is %s,after strcat(s1,s2) is %s, s2 is %s\n", s1, s3, s2);

    printf("strlen(s1) is %d\n", strlen(s1));

    printf("strcmp(%s,%s) is %d\n", s1, s1, strcmp(s1, s1));
    printf("strcmp(%s,%s) is %d\n", s1, s2, strcmp(s1, s2));//s2 is bad for s3's length is not enough

  }
strcopy(s3,s1) s3 is hello
s1 before is hello ,after strcat(s1,s2) is hello liuliancao, s2 is iancao
strlen(s1) is 6
strcmp(hello ,hello ) is 0
strcmp(hello ,iancao) is -1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
  #include <stdio.h>
  #include <string.h>

  int main() {
    char s1[20] = "hello "; // not s1[6], will show not recognized char
    char s2[20] = "liuliancao";

    char s3[20] = {""};
    strcpy(s3, s1);
    printf("strcopy(s3,s1) s3 is %s\n", s3);

    strcat(s3, s2);
    printf("s1 before is %s,after strcat(s1,s2) is %s, s2 is %s\n", s1, s3, s2);

    printf("strlen(s1) is %d\n", strlen(s1));

    printf("strcmp(%s,%s) is %d\n", s1, s1, strcmp(s1, s1));
    printf("strcmp(%s,%s) is %d\n", s1, s2, strcmp(s1, s2));

  }
strcopy(s3,s1) s3 is hello
s1 before is hello ,after strcat(s1,s2) is hello liuliancao, s2 is liuliancao
strlen(s1) is 6
strcmp(hello ,hello ) is 0
strcmp(hello ,liuliancao) is -4

注意几个点:

  • 字符串的声明时候要注意留一个给'\0',否则会存在越界行为,
  • strcopy(目的,源) 需要注意方向,copy不会影响源,但是后续请尽量使用copy的值
  • strcat(s3,s2) 这个会让s3和s2拼接在一起,和cat命令一样, 这里一定要注意s3的大小要够(如果计划要cat)
strchr && strstr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  #include <stdio.h>
  #include <string.h>

  int main ()
  {
    char s1[20] = "liuliancao";
    char ch = 'u';//must single quotation
    char s2[] = "iu";
    printf("after %c(include) is %s\n", ch, strchr(s1, ch));
    printf("after %s is %s\n", s2, strstr(s1, s2));
    return 0;
  }
after u(include) is uliancao
after iu is iuliancao

注意这里需要注意一点是使用字符的时候,一定要单引号,否则是字符串

枚举enum

枚举,比如性别分为男和女,星期分为1到7

一个简单的例子,来自菜鸟c

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

  enum Day {
    MON=1, TUE, WEN, THR, FRI, SAT, SUN
  };

  enum Day day;

  enum Gender {
    WOMAN=1, MAN=2
  } gender;
  day = THR;

  printf("THR is %d\n", day);

  gender = WOMAN;
  printf("You said %d\n", gender);
THR is 4
You said 1
结构体struct

结构体,用户可以自定义的一种数据类型,一个结构体通常包含各种各样的字段,这些组成了这个结构, 是组成的关系

以Employee为例,包含ID, 英文姓名,显示姓名,性别,邮件等, 用结构体定义如下

 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
  #include <stdio.h>
  #include <string.h>
  enum Gender {
    WOMAN=0,MAN=1
  };

  struct Employee {
    int id;
    char name[30];
    char displayName[30];
    enum Gender gender;
    char mail[50];
  };
  struct Employee xiaoA;
  xiaoA.id = 1;
  strcpy(xiaoA.name, "XiaoA");
  strcpy(xiaoA.displayName, "小A");
  xiaoA.gender = MAN;
  strcpy(xiaoA.mail, "xiaoa@liuliancao.com");

  printf("Employee(xiaoA)\n");
  printf("id: %d\n",xiaoA.id);
  printf("name: %s\n",xiaoA.name);
  printf("displayName: %s\n",xiaoA.displayName);
  printf("gender: %d\n",xiaoA.gender);
  printf("mail: %s\n",(&xiaoA)->mail);
Employee(xiaoA)
id: 1
name: XiaoA
displayName: 小A
gender: 1
mail: xiaoa@liuliancao.com

总结:

  • 结构体类似类,可以包我们需要的东西来实现抽象
  • 结构体对象的访问内容方式是NAME.ATTRIBUTE
  • 结构体指针的访问内容方式P->ATTRIBUTE, 注意(&xiaoA)->mail这个,需要括号
  • 结构体一般建议传指针(地址)
  • 注意string的赋值,使用string库里面的strcpy完成赋值
类型定义typedef

给类型起一个名字,这样可以直接使用这个名字表示这个类型

typedef 原有类型 我定义的名字;

1
2
3
4
5
  #include <stdio.h>
  typedef unsigned int Z;

  Z a = 5;
  printf("a is %d", a);
a is 5

clangd

  liuliancao@lqx:~$ sudo apt-cache search clangd
  clang-tools-6.0 - clang-based tools for C/C++ developments
  clang-tools-7 - clang-based tools for C/C++ developments
  liuliancao@lqx:~$ sudo apt-get install clang-tools-7 -y
  liuliancao@lqx:~$ sudo update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-7 100
  update-alternatives: using /usr/bin/clangd-7 to provide /usr/bin/clangd (clangd) in auto mode