简单示例

# include <stdio.h>

/* 
 * 注释语句
 */
int main(void) {
    int num = 3;
    char letter = 'b';
    printf("num = %d, letter = %c", num, letter);
    return 0;
}

数据类型

整型

char, short int, int, long int, long long int 除了char型,这些整数类型若未指明是signed或unsigned,都表示signed

浮点型

float, double, long double

运算符

位运算

&, |, ^, ~

移位运算

«, »

算术运算符

+, -, *, /, %

赋值运算符

=, +=, *=, /=, %=, «=, »==, &=, |=, ^=

比较运算符

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

逻辑运算符

&&, ||, !

选择语句

if..else if..else

#include <stdio.h>
int main(void) {
    int score = 88;

    if (score >= 90) {
        printf("great");
    } else if (score >= 70) {
        printf("good");
    } else if (score >= 60) {
        printf("pass");
    } else {
        printf("bad");
    }

    return 0;
}
good

switch

#include <stdio.h>
int main(void) {
    int day = 2;

    switch(day) {
    case 1:
        printf("Monday");
        break;
    case 2:
        printf("Tuesday");
        break;
    default:
        printf("no");
        break;
    }
}
Tuesday

循环语句

while / do..while

#include <stdio.h>
int main(void) {
    int num = 0;
    while(num <= 3) {
        printf("%d\n", num);
        num += 1;
    }
    return 0;
}
0
1
2
3
#include <stdio.h>
int main(void) {
    int num =0;
    do {
        printf("%d\n", num);
        num += 1;
    } while(num <=3 );
    return 0;
}
0
1
2
3

for

#include <stdio.h>
int main(void) {
    for(int i=0; i<=3; i++) {
        printf("%d\n", i);
    }
    printf("-------------\n");

    // break
    for(int i=0; i<=3; i++) {
        if(i==2) break;
        printf("%d\n", i);
    }
    printf("-------------\n");

    // continue
    for(int i=0; i<=3; i++) {
        if(i==2) continue;
        printf("%d\n", i);
    }
    return 0;
}
0
1
2
3
-------------
0
1
-------------
0
1
3

goto

#include <stdio.h>
int main(void) {
    printf("1\n");
    printf("2\n");
    goto error;
    printf("3\n");
    printf("4\n");

    error:
        printf("this is error segment\n");
}
1
2
this is error segment

函数

带参数和返回值

#include <stdio.h>

// 若函数定义在被调用之后,要声明
int add_num(int, int);

int main(void) {
    int x = 10, y = 20;
    printf("%d\n", add_num(x, y));
    return 0;
}

int add_num(int num1, int num2) {
    return num1 + num2;
}
30

结构体

简单示例

/*
 * 结构体定义在函数内
 */

#include <stdio.h>
#include <string.h>

int main(void) {
    struct student {
        int id;
        char name[10];
        char gender;
        float score;
    };

    // 声明后赋值
    struct student s1;
    s1.id = 10;
    strcpy(s1.name, "zhubiao");
    s1.gender = 'M';
    s1.score = 99.9;  
    printf("%s: %f\n", s1.name, s1.score);

    // 声明时赋值
    struct student s2 = {11, "zhangya", 'F', 82.5};
    printf("%s: %f\n", s2.name, s2.score);

    // 定义结构体时就声明变量
    struct complex_struct {
        double x;
        double y;
    } z1, z2;
    z1.x = 10.3;
    z1.y = 15;
    printf("%f, %f", z1.x, z1.y);

    return 0;
}
zhubiao: 99.900002
zhangya: 82.500000
10.300000, 15.000000
/*
 * 结构体定义在函数外
 */

#include <stdio.h>
#include <string.h>

struct student {
    int id;
    char name[10];
    char gender;
    float score;
};

int main(void) {
    struct student s1 = {1001, "zhubiao", 'M', 99.5};
    printf("%s: %f\n", s1.name, s1.score);

    return 0;
}
zhubiao: 99.500000

结构体嵌套

#include <stdio.h>

struct t_fs_struct {
    // 进程与文件系统的关系
    int users;
    int umask;
};

struct t_files_struct {
    // 打开的文件集
    int max_fds;
    int max_fdset;
    int next_fd;
};

struct t_task_struct {
    // 进程描述符
    int pid;
    int gid;
    struct t_fs_struct fs;
    struct t_files_struct files;
};

int main(void) {
    struct t_task_struct task1 = {
        1001,
        2001,
        {33, 222},
        {65535, 65535, 20}
    };

    printf("umask = %d\n", task1.fs.users);
    printf("next_fd = %d\n", task1.files.next_fd);

    return 0;

}
umask = 33
next_fd = 20

数组和字符串

数组

数组是一种复合类型,由一些列相同类型的元素组成,和结构体一样各个元素在内存中的存储空间是相邻的,元素可以是基本类型,也可以是复合类型,甚至指针类型,如int, char, struct, int *等。

/*
 * 数组元素可以是基本类型,也可以是复合类型
 */
#include <stdio.h>

struct f_task_struct {
    int pid;
    int gid;
};

int main() {
    // 元素是基本类型
    int nums[5];
    nums[0] = 10;
    nums[2] = 20;

    // 元素是结构体类型
    struct f_task_struct tasks[3];
    tasks[0].pid = 1001;
    tasks[0].gid = 1002;
    tasks[1].pid = 2001;
    tasks[1].gid = 2002;
}
/*
 * 数组元素地址是相邻的
 */
#include <stdio.h>

int main() {
    int nums[5], i;

    for(i=0; i<sizeof(nums)/sizeof(nums[0]); i++) {
        // 打印各个元素的地址
        printf("%d\n", &nums[i]); 
    }
    return 0;
}
1322353632
1322353636
1322353640
1322353644
1322353648
/*
 * 数组名存储的是第一个元素的地址
 */
#include <stdio.h>

int main(void) {
    int nums[5];

    printf("%d: %d", nums, &nums[0]);
    return 0;
}
226477552: 226477552

字符串

“hello” 在内存中的存储: image.png

字符串类似存储字符的数组
字符串与字符数组相同之处:

a. 都可以通过下标访问  
b. 字符串中的每个字符存储都是相邻的  
c. 做右值计算时,自动转换成指向首元素的指针,类似于数组名

字符串与字符数组不同之处:

a. 字符数组是可以改变元素的值,而字符串是只读的。 
/*
 * 字符串
 */

#include <stdio.h>

int main(void) {
    char s1[] = {'h', 'e', 'l', 'l', 'o', '\n'};

    // 字符串可以通过下标访问
    printf("%c\n", "hello"[0]);

    // 做右值计算,自动转换为指向首元素的指针
    char s2[] = "hello";

    // 字符串默认会在尾部添加一位'\0', 也就是ASCII的NULL字符。
    printf("%d\n", sizeof("hello"));

    // "hello"[0] = 'a'; 会报错,字符串是只读的
    return 0;
}
h
6
/*
 * printf("%s");
 */

#include <stdio.h>

int main(void) { 
    char a[10] = {'h', 'e', '\0', 'l', 'l', 'o'};
    char b[] = "good";
    char *c;
    c = "nice";

    // 用printf中,若用%s做占位符,则只要告诉其元素的首地址,printf会一直打印到元素为'\0'为止的元素。
    printf("%s\n", a);
    printf("%s\n", b);
    printf("%s\n", c);
}
he
good
nice

指针

内存单元中保存的若不再是常规的数据,而是指向另外一个内存单元的地址,该内存单元称为指针。
int i;, 则i: 为该内存单元中存储的数据,而&i: 为该内存单元的地址。
指针本质是一个整数,是数组的索引。

简单示例

point.png

#include <stdio.h>

int main(void) {
    // 指针声明在变量前面写*, 
    // 除了声明外,赋值等其他地方若变量前有*,则表示取指针存储的地址所指向内存单元中所存的数据。
    int *pi;
    int num = 10;
    pi = &num;
    printf("%d\n", *pi);

    *pi = *pi + 10;
    printf("%d\n", num);
    return 

输出

10
20

指针与数组

数组名与指针的关系:
相同之处:

  • 在表达式中使用时,数组名相当于一个指针常量,其存储的是数组第一个元素的地址,不可改变其值。

不同之处:

  • 使用sizeof(数组名) 得到的结果是数组所有元素的长度之和,而不是指针的长度。
  • 使用&数组名取地址时是指向数组地址的指针。
#include <stdio.h>

int main(void) {
    int scores[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int *ps, i;

    ps=&scores[0];
    for(i=0; i<=9; i++) {
        printf("%d: %d: %d: %d: %d\n", &ps, ps, &scores[0], &scores[1]);
        // ps++所增加的地址是由声明的类型决定的,此处是int, 每次增加4。
        ps++;
    }

    // 数组名存储的值位数组第一个元素的地址。
    printf("address of scores: %d", scores);
}

输出

-948465336: -948465328: -948465328: -948465324: 25653296
-948465336: -948465324: -948465328: -948465324: -907652787
-948465336: -948465320: -948465328: -948465324: -907652787
-948465336: -948465316: -948465328: -948465324: -907652787
-948465336: -948465312: -948465328: -948465324: -907652787
-948465336: -948465308: -948465328: -948465324: -907652787
-948465336: -948465304: -948465328: -948465324: -907652787
-948465336: -948465300: -948465328: -948465324: -907652787
-948465336: -948465296: -948465328: -948465324: -907652787
-948465336: -948465292: -948465328: -948465324: -907652787
address of scores: -948465328

指针与const限定符

#include <stdio.h>

int main(void) {
    // int const *pa 或 const int *pa声明一个指向const int型的指针,
    // pa所指向的地址存储的值不可改变,但指针存储的地址可以改变
    int const *pa, a=10;
    pa = &a;
    pa++;
    // *pa = 3;报错

    // 字符串在做右值计算的时候,自动转换为指向首元素的指针
    char w1[50] = "good";
    char *w2 = "nice";
    printf("%s", w2);

    return 0;
}

输出

nice

函数返回值类型是指针

#include <stdio.h>

// 声明函数的返回的值类型是int指针,传入的参数也是int指针类型。
int *exchange(int *num1, int *num2){
    int *tmp = num1;
    num1 = num2;
    num2 = tmp;
    return num1;
}

int main() {
    int a, b;
    int *result;
    a = 10;
    b = 20;
    result = exchange(&a, &b);
    printf("%d", *result);
}

输出

20

指针与结构体

#include <stdio.h>

struct t_files_struct {
    int max_fds;
    int next_fd;
} f1 = {65536, 20};

struct t_task_struct {
    int pid;
    int gid;
    struct t_files_struct *files;
} task1;

int main(void) {
    task1.files = &f1;

    // (*task1.files).next_fd 可简写为: task1.files -> next_fd
    printf("%d", task1.files -> next_fd);
}

输出

20

指向指针的指针

#include <stdio.h>

int main(void) {
    int **p1, *p2, p3;
    p3 = 100;
    p2 = &p3;
    p1 = &p2;
    printf("%d: %d: %d\n", p3, *p2, **p1);
    printf("%d: %d: %d\n", p1, p2, p3);
}
100: 100: 100
-1676064400: -1676064404: 100

指针数组

数组中可以存储基本类型、也可存储指针类型或者复合类型。当存储的是指针类型就是指针数组
image.png

#include <stdio.h>

int main(void) {
    int a, b, c;
    int *nums[] = {&a, &b, &c};
    a = 10;
    b = 20;
    c = 30;

    // 打印指针数组中存储的地址值和该地址指向的内存中的值
    printf("%d: %d", nums[0], *nums[0]);
    return 0;
}
2145752524: 10
/*
 * 指针数组存储的若是字符串的首地址,则用printf(%s)打印将会很有趣
 */
#include <stdio.h>

int main(void) {
    // 字符串右值赋值是取首元素的地址。所以才有下面的写法.
    char *s[] = {
        "hello", 
        "word",
        "nice"
    };

    // 打印%s只需要告诉其首地址,他就会打印该地址内存储的元素及其后续的元素,直到遇到元素为\0的才停止。
    printf("%s\n", s[0]);
    printf("%c\n", *s[0]);
    return 0;
}
hello
h

函数指针

函数名:

a. 和数组名类似,函数名是指向函数入口的指针
b. sizeof(函数名), &函数名, *函数名:此时函数名是指函数本身。
#include <stdio.h>

void ftest(void) {
    int i;
}

int main(void) {
    // 函数名是指向函数入口地址的指针
    void (*f)();
    f = ftest;

    // &函数名,*函数名:都是取函数地址
    printf("%d: %d: %d", ftest, *ftest, &ftest);    
}
1901139621: 1901139621: 1901139621

参考文档

Linux C编程一站式学习