C语言整理(待更新)

C语言整理(待更新)

Note:根据 CSDN C语言技能树 整理的题目;初衷是因为C语言相对于Java,Python更加简洁,没有那么多花里胡哨的语言特性,用来练习算法题再合适不过了。

一、C语言的发展

二、数据类型

Note

  • C语言没有bool,string类型;基本类型包括字符型,浮点型,整型 和 空类型;派生类型包括数组,指针和函数类型;用户自定义类型包括枚举,结构体和联合体类型。
  • 不同位数的操作系统,基本类型所占内存大小也是不同的。比如64位(机器字为8字节)系统int类型可能为4 或 8,16位系统int类型为2字节。
  • C++的基本类型则在C语言基本类型基础上,增加了bool类型
  • C语言规定,结构体中各成员列表占用连续内存空间,但结构体的大小并不是简单地将每个结构体成员的大小相加就能得到,需要进行内存对齐,不同编译器默认对齐数不同(VS为8),而对齐倍数由编译器默认对齐数(8) 和 变量类型的字节数(int 4,char 1)中的最小值(4,1)决定,而对齐数由结构体(int 4,char 1)中最大字节数(4)决定,比如下面结构体中对齐数为4。
    struct S1
    {
    	char a;  //1 byte
    	char b;  //1 byte
    	int c;  //4 byte
    };
    
    即使是64位系统的long long,也只有最多的8 byte占一个字长
    数据对齐意味着将数据放在小于或等于字长的倍数的内存偏移处,这由于CPU处理内存的方式而提高了系统的性能。大多数CPU只能访问内存对齐的地址。
    操作系统在数据读取的时候,并不是一个字节一个字节进行读取的,而是一段一段进行读取,如果没有对齐约束,代码可能最终不得不跨越机器字边界进行两次或多次访问(有些硬件架构直接报错)。因此考虑平台和性能原因,数据结构(尤其是栈),结构体应该尽可能的在自然边界上对齐,这样访问一个变量只需访问1次内存即可,结构体的内存对齐是拿空间来换取时间的做法。
    struct S1
    {
    	char a;  //1 byte
    	char b;  //1 byte
    	int c;  //4 byte
    };//结构体1
    struct S2
    {
    	char a;  //1 byte
    	int c;  //4 byte
    	char b;  //1 byte
    };//结构体2
    
    结构体1和结构体2的成员变量一模一样,可是当我们按照内存对齐规则来计算两个结构体的大小的时候,会发现两个结构体的大小不一样,在VS编译器下(通过对上面结构体的计算,对齐数为4)第一个结构体大小为8(变量起始地址依次为0,1,4,4 + 4 = 8),第二个结构体大小为12(不是9,也不是16,变量起始地址依次为0,4,8,8 + 4 = 12)。解释见结构体中的对齐数到底是什么
    内存对齐更多知识可参考详聊内存对齐(Memory alignment)结构体内存对齐(如何计算结构体的大小)失传的C结构体打包技艺
  • 内存连续分配方式包括:单一连续分配,固定分区分配(预先划分分区大小,大小可以不等),动态分区分配(首次适应,最佳适应,最坏适应,邻近适应)。为结构体分配内存空间时采用动态分配 + 内存对齐

三、运算符与表达式

四、语句与控制流

五、函数与程序结构

六、数组(派生数据类型)

Note

  • 数组名,即数组地址,数组元素首地址。 参考C语言:用指针访问数组元素
  • 二维数组的存储方式其实在内存中是用一个连续的存储空间存起来的,而二维数组的表示是通过行地址和列地址构成的,i行的地址和第i行第0列地址的是相同的;参考二维数组的行地址、列地址,与元素的存储
    int arr[2][5] = {1,2,3,4,5,6,7,8,9,10};
    cout << "arr首行地址:" << arr << " ,arr首行首列地址" << &arr[0][0] << endl;
    cout << "arr第二行地址:" << arr[1] << " ,arr第二行首列地址" << &arr[1][0] << endl;
    ---
    arr首行地址:0x40d67ff8f0 ,arr首行首列地址0x40d67ff8f0
    arr第二行地址:0x40d67ff904 ,arr第二行首列地址0x40d67ff904
    

七、指针(派生数据类型,用地址初始化,用*解析地址中的值)

Note

  • 对于指针变量,我们可以把它类比看做是计算机指令,我们从指针变量中取出已存放变量(操作数,操作数组等)的地址(操作数寻址),接着使用*对地址进行解析(取操作数);如果是操作数组,则可以通过++操作实现连续地址空间的访问(获取下一个操作数的地址),但是这样容易出现访问越界问题。

  • 假设a是一个整型变量,p为指针变量,指向a的地址,q为指针变量,指向p指针的地址

    //写法1
    int a = 10;  //整型变量
    int *p = &a;  //指针变量,指向整型变量地址
    int **q = &p;  //二级指针变量,指向指针变量地址
    cout << "a值为:" << a << ", a的地址为:" << &a << endl;
    cout << "p值为:" << p << ", *p为:" << *p << ", *p的地址为:" << &p << endl;
    cout << "q值为:" << q << ", *q为:" << *q << ", **q为:" << **q << ", q的地址为:" << &q << endl;
    ---
    a值为:10, a的地址为:0x4dff1ffbec
    p值为:0x4dff1ffbec, *p为:10, *p的地址为:0x4dff1ffbe0
    q值为:0x4dff1ffbe0, *q为:0x4dff1ffbec, **q为:10, q的地址为:0x4dff1ffbd8
    
    //写法2
    int a = 10;  //整型变量
    int *p;  //指针变量
    p = &a;
    int **q;  //二级指针变量
    q = &p;
    cout << "a值为:" << a << ", a的地址为:" << &a << endl;
    cout << "p值为:" << p << ", *p为:" << *p << ", *p的地址为:" << &p << endl;
    cout << "q值为:" << q << ", *q为:" << *q << ", **q为:" << **q  << ", q的地址为:" << &q << endl;
    ---
    a值为:10, a的地址为:0x1c577ff97c
    p值为:0x1c577ff97c, *p为:10, *p的地址为:0x1c577ff970
    q值为:0x1c577ff970, *q为:0x1c577ff97c, **q为:10, q的地址为:0x1c577ff968
    
  • 假设b是二维数组,p为指针变量,指向b首地址

    int row = 2, col = 5;
    int arr[row][col] = {{1,2,3,4,5},{6,7,8,9,10}};
      
    int *p;
    p = &arr[0][0];
    int num = row * col;
    int i;
    for(p = &arr[0][0], i = 1; i <= num; p++, i++){
        cout << *p << ",";
        if(i % col == 0){
            cout << endl;
        }
    }
    ---
    1,2,3,4,5,
    6,7,8,9,10,
    
  • 数组指针是一个指针变量,占有内存中一个指针的存储空间

    int arr[5]={1,2,3,4,5};
    int (*p1)[5] = &arr;
    /*下面是错误的*/
    int (*p2)[5] = arr;
    

    指针数组是多个指针变量,以数组的形式存储在内存中,占有多个指针的存储空间

    char *fruits[LEN] = {
        "apple",
        "cherry",
        "grape",
        "peach",
        "watermelon"
    	 };
    

    假设arr1为数组1,arr2为数组2,arr为指向arr1arr2的指针数组(arr1arr2长度可以不等):

    //指针数组(有点像邻接表)
    int arr1[5] = {1,2,3,4,5};
    int arr2[3] = {1,2,3};
    int *arr[2] = {arr1, arr2};
    int *p = arr[0];  //数组首行指针元素指向的字符串首地址
    cout << "arr1数组地址为:" <<  arr1 << ", arr1数组首元素地址为:" << &arr1 << ", arr1数组首元素值为:" << arr1[0] << endl;
    cout << "arr[0]值为:" << arr[0] << ", arr数组首地址为:" << arr  << ", arr数组首指针元素地址为:" << &arr[0] << ", arr[0]解析的值为" << *arr[0] << endl;
    cout << "p值为:" << p << ", *p为:" << *p << ", p的地址为:" << &p << endl;
    
    int i = 0;
    for(p = arr[0]; i < 2; i++, p = arr[i]){
        cout << "第" << i + 1 << "行: " << endl;
        do{
            cout << "p的地址为:" << p << ", p值为:" << *p << endl;
            if(*(p + 1) == 0){
                break;
            } //没办法,还是无法解决指针访问地址下标越界
        }while(p++);
    }
    ---
    arr1数组地址为:0x52cbfff8a0, arr1数组首元素地址为:0x52cbfff8a0, arr1数组首元素值为:1
    arr[0]值为:0x52cbfff8a0, arr数组首地址为:0x52cbfff880, arr数组首指针元素地址为:0x52cbfff880, arr[0]解析的值为1
    p值为:0x52cbfff8a0, *p为:1, p的地址为:0x52cbfff8781:
    p的地址为:0xd32d9ffde0, p值为:1
    p的地址为:0xd32d9ffde4, p值为:2
    p的地址为:0xd32d9ffde8, p值为:3
    p的地址为:0xd32d9ffdec, p值为:4
    p的地址为:0xd32d9ffdf0, p值为:5
    p的地址为:0xd32d9ffdf4, p值为:565
    p的地址为:0xd32d9ffdf8, p值为:162:
    p的地址为:0xd32d9ffdd4, p值为:1
    p的地址为:0xd32d9ffdd8, p值为:2
    p的地址为:0xd32d9ffddc, p值为:3
    p的地址为:0xd32d9ffde0, p值为:1
    p的地址为:0xd32d9ffde4, p值为:2
    p的地址为:0xd32d9ffde8, p值为:3
    p的地址为:0xd32d9ffdec, p值为:4
    p的地址为:0xd32d9ffdf0, p值为:5
    p的地址为:0xd32d9ffdf4, p值为:565
    p的地址为:0xd32d9ffdf8, p值为:16
    p的地址为:0xd32d9ffdfc, p值为:1
    p的地址为:0xd32d9ffe00, p值为:1
    

    指针数组中的每一个元素(arr[0]arr[1]),其实是各个一维数组的首地址(可以初始化,也可以malloc),不是指针变量的地址,指针变量地址应为&arr[0],&arr[1],指针变量只是指向了这些数组的首地址,避免了空指针问题。
    字符指针数组 的使用参考【C语言】字符串数组按字典升序C语言指针数组初始化

  • *&是相反操作:ptr = &fruits[0]; *ptr = fruits[0],这里要注意指针数组也是数组,依然满足指针数组的数组名(fruits)为第一个指针元素的首地址(&fruits[0],但不等于fruits[0]。参考交换变量值2(我的题解)

    //定义指针数组
    char *fruits[LEN] = {
        "apple",
        "cherry",
        "grape",
        "peach",
        "watermelon"
    };
    //等价于 char **ptr = fruits; 也等价于 char **ptr = &fruits[0];
    char **ptr;
    ptr = &fruits[0]; //指针地址,而*ptr为指针地址中存储的值,首行第一个字符串的首地址
    for (int i = 0; i < LEN; ++i, ptr++)
    {   
        printf("%s\n",*ptr);  //ptr表示fruits首行第一个指针元素的地址, *ptr表示fruits首行第一个指针元素的值,即首行第一个字符串的首地址。
    }
    ---
    apple
    cherry
    grape
    peach
    watermelon
    
  • *p++*(p++)(*p)++*++p++*p 的运算规则 参考指针&指针值的自增与自减(结合自增运算符 A = i++, B = ++i 理解)

  • 原则上指针变量可以从地址的角度,访问所有变量(基本类型变量,数组,函数),操作所有变量;但是实际上对于指针的使用,还是得结合特定场景(链表实现等),不要一味追求指针,否则很容易弄巧成拙。因为任何变量类型可以转换成指针类型,但是指针类型不能转化成指定的变量类型,比如字符串等价于字符数组,而不等于字符指针(下一条有说明)。

  • 数组和指针是不同的数据类型,有本质的区别:char str[] = "abcd"; //sizeof(str) == 5 * sizeof(char),而char *str = "abcd"; //sizeof(str) == 4(x86) or 8(x64)
    char *str = "xxxxx",str指向该常量地址(静态存储区);char str[] = "xxxxx",str在上申请空间,将常量内容复制进来,所以是局部变量,允许字符串strcpy操作;参考char str[] 和 char *str 的区别C 语言 strcpy 报 segmentation fault动态存储区、静态存储区、堆和栈的区别

  • 指针比较难理解,在了解基础概念之后一定要多加练习,不然很容易整迷糊。

八、字符串

九、结构体(用户自定义数据类型)

Note

  • 声明和初始化结构体的几种方式:注意结构体声明时末尾带;
    //声明结构体方法1:只能创建一个实例stu;struct为结构体类型符,Student是结构体的类型名;最后的分号一定要写
    struct Student
    {
        char *name;
        int id;
        unsigned int age;
        char group;
        float score;
    } stu = {"张三", 1001, 16, 'A', 95.50};
    
    //声明结构体方法2:typedef将结构体`struct Student`重命名为student或者Stu,其中`struct Student`可以简写为`struct`;
    typedef struct Student
    {
        char *name;
        int id;
        unsigned int age;
        char group;
        float score;
    }student,Stu;
    
    //初始化方法1(struct关键字可省略)
    Student stu = {"张三", 1001, 16, 'A', 95.50};
    //初始化方法2
    Stu stu1 = {"张三", 1001, 16, 'A', 95.50};
    //初始化方法3(在main函数中为结构体成员赋值)
    struct Stu stu2;
    
    int main(int argc, char** argv)
    {      
        stu2.name = "张三";
        stu2.id = 1001;
        stu2.age = 16;
        stu2.group = 'A';
        stu2.score = 95.50;
    
        printf("========== 学生基本信息 ==========\n");
        printf("姓名:%s\n学号:%d\n年龄:%d\n所在小组:%c\n成绩:%.2f\n",
               stu2.name, stu2.id, stu2.age, stu2.group, stu2.score);
        printf("==================================\n");
    
        return 0;
    }
    
  • 如果使用结构体声明方法1来定义结构体数组,而不是通过结构体名定义结构体数组时,只能在结构体定义时同时对数组初始化
    错误做法如下,会报编译错误:error: assigning to an array from an initializer list
    #include <stdio.h>
    #define NUM_STR 3
    
    struct Student
    {
        char *name;
        int id;
        unsigned int age;
        char group;
        float score;
    } cls[NUM_STR];
    
    int main(int argc, char** argv)
    {
        cls = {
            {"张三", 1001, 16, 'A', 95.50},
            {"李四", 1002, 15, 'A', 90.00},
            {"王五", 1003, 16, 'B', 80.50}
        };
        return 0;
    }
    
    正确做法如下
    #include <stdio.h>
    #define NUM_STR 3
    
    struct Student
    {
        char *name;
        int id;
        unsigned int age;
        char group;
        float score;
    } cls[] = {
        "张三", 1001, 16, 'A', 95.50,
        "李四", 1002, 15, 'A', 90.00,
        "王五", 1003, 16, 'B', 80.50
    };
    
    int main(int argc, char** argv)
    {
    	return 0;
    }
    
  • 指针访问对象 :student->name(*student).name,不能是student++
  • 想要定义结构体类型的指针一定要用typedef

十、联合体与枚举类型(用户自定义类型)

Note

  • 定义枚举类型
    1)格式:enum 枚举类型 {枚举值列表}
    2)枚举值列表可以手动设置,也可以默认从0递增;
    3)如果在全局下定义枚举类型,则枚举类型中所有的枚举值均为全局变量
    //1、定义枚举类型:
    //枚举颜色(red, orange, yellow, green, ching, blue, purple为枚举类型变量,如果在全局下定义枚举类型,则枚举类型中所有的枚举值均为全局变量,值为整型;如果有指定enum值,则将指定的enum值赋予enum变量)
    enum color{red=1, orange=2, yellow=3, green=4, ching=5, blue=6, purple=7};
    
    //枚举一个星期的每一天(默认从0开始递增得到enum值)
    enum week
    {
    	Su, Mo, Tu, We, Th, Fr, Sa
    };
    
    //枚举每一个月
    enum month
    {
    	January, February, March, April, May, June, July, August, September, October, November, December
    };
    
    int main(int argc, char const *argv[])
    {
        printf("%-3d %-3d %-3d %-3d %-3d %-3d %-3d\n", red, oreange, yellow, green, ching, blue, purple); 
        printf("%-3d %-3d %-3d %-3d %-3d %-3d %-3d\n", Su, Mo, Tu, We, Th, Fr, Sa); 
        printf("%-3d %-3d %-3d %-3d %-3d %-3d %-3d %-3d %-3d %-3d %-3d %-3d\n", January, February, March, April, May, June, July, August, September, October, November, December); 
        return 0;
    }
    ---
    1   2   3   4   5   6   7  
    0   1   2   3   4   5   6
    0   1   2   3   4   5   6   7   8   9   10  11 
    
  • 定义枚举类型变量
    //枚举一个星期的每一天(默认从0开始递增得到enum值)
    enum week
    {
    	Su, Mo, Tu, We, Th, Fr, Sa
    };
    //定义枚举类型的变量
    enum week a = Su;
    enum week b = Mo;
    enum week c = Fr;
    int main(int argc, char const *argv[])
    {
        printf("%-3d %-3d %-3d\n", a, b, c); 
        return 0;
    }
    ---
    0   1   5
    

十一、位运算

十二、预处理器

十三、文件

十四、存储管理

十五、标准函数库

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值