达内C语言笔记DAY12

回顾:

1. 大型程序开发代码分类

  1. 头文件卫士

    vim A.h
    #ifndef __A_H
    #define __A_H
    	头文件内容
    #endif
    
  2. 大型程序代码分为3部分

    头文件:

    ​ 各种声明

    ​ 全局变量声明

    ​ 函数声明

    ​ 结构体声明

    ​ 联合体声明

    ​ 枚举声明

    源文件

    ​ 各种定义

    ​ 全局变量(普通变量,数组,结构体,联合体,枚举)定义

    ​ 函数定义

    主文件

    ​ 调用管理,包含main函数

    ​ 调用变量和函数之前包含对应的头文件进行声明

2. 结构体

2.1 目前C语言分配内存的方式

定义变量,数组,结构体,联合体

  1. 为何用结构体分配内存

    既可以做到大量分配内存,也可以做到数据类型不一致

  2. 结构体声明和定义

    先声明后定义

    struct A {
    	成员;
    };
    

    先用typedef对声明的结构体取别名然后定义

    typedef struct A {
    	成员;
    } A_t;
    A_t a;
    
  3. 结构体初始化

    传统初始化

    ​ 全部按照顺序初始化

    A_t a = {, , , ,};
    

    标准初始化

    ​ 不要按照顺序也不要全部初始化

    A_t a = {各种.};
    
  4. 结构体成员访问

    结构体变量名.成员名;

    结构体变量的指针->成员名

  5. 结构体嵌套(包含)

    结构体嵌套结构体

    ​ 成员的访问:A.B.成员名;

    ​ A->B.成员名;

    结构体包含结构体变量的指针

    ​ 成员的访问:A.B->成员名

    ​ A->B->成员名

    注意:B必须指向一块内存区域

  6. 结构体和数组的配合

    数组中每个元素是一个结构体

    玩法:数组结合结构体

  7. 结构体和函数的配合

    只要将来编写一个函数访问操作结构体,此函数的形参一定是一个结构体指针变量

    两点原因:指针变量将来永远只分配4字节,节省内存

    ​ 将来函数通过指针修改结构体成员,否则只能查看不能修改(会=回想之前的swap函数)

    void A(A_t *p) {
    	....
    } A_t;
    

    函数的返回值当然也可以用结构体,可以返回一个结构体变量,也可以返回一个结构体指针(不能是局部非静态)

    一般建议返回一个结构体指针(只需4字节)

3. 联合体

3.1 玩法和结构体一模一样,关键字union

3.2 所有成员共用一块内存


4. 枚举

4.1 本质

枚举的本质就是一堆整数的集合,更确切的来说,也就是给一堆得整数取了个别名而已,提高代码的可读性

例如:0表示红色,1表示绿色,2表示蓝色,过段时间可能就混淆了数字和颜色的对应关系

​ 干脆给0,1,2三个数字取别名叫:RED, GREEN, BLUE,不管什么时候,只要看到别名,就能匹配到对应的颜色上

​ 跟#define宏类似

#defin	RED		0
#define GREEN	1
#define		BLUE	2

结论:以后给整型数字取别名两种方法:采用枚举或者宏

4.2 特点

枚举值默认从0开始,后面的成员依次加1

枚举一般建议用大写

4,3 声明枚举数据类型的语法:

enum 枚举数据类型名(也可以不用) {枚举值,如果有多个用逗号分开};
例如:
enum COLOR {RED, GREEN, BLUE};
结果:
RED = 0, GREEN = 1, BLUE = 2	也就是0,1,2三个数字取别名
printf("%d %d %d\n", RED, GREEN, BLUE);

enum COLIR {RED, GREEN=250, BLUE};
结果:
RED = 0, GREEN = 250, BLUE = 251	也就是0,250,251三个数字取别名
参考代码:enum.c
/*枚举演示*/
#include <stdio.h>

int main(void)
{
    enum {RED, GREEN=250, BLUE};
    printf("%d %d %d\n", RED, GREEN, BLUE);  // 可读性很好
    printf("RED+GREEN+BLUE=%d\n", RED+GREEN+BLUE);

    enum RESULT {RETURN_FILED, RETURN_OK};
    printf("%d, %d\n", RETURN_FILED, RETURN_OK);

    return 0;
}

4.4 枚举经典使用模板(操作系统核心代码片段)

vim enum2.c 添加
#include <stdio.h>

/*定义检测变量值的函数*/
int check(int a)
{
	if(a != 0) {
	printf("表示成功了\n");
	return 0;   //表示成功了
} else {
	printf("表示失败了\n")
	return 1; // 表示失败了
}
}

int main(void)
{
	printf("%d\n", chenk(0));
	printf("%d\n", check(1))
	return 0;
}
结论:程序员很难判断0和1谁是成功,谁是失败,
除非认真仔细研究透代码,或者有详细的代码注释,
否则代码的可读性很差

解决办法:利用枚举或者#define宏提高代码的可读性,这里采用枚举

改进之后的经典代码(公式):

vim enum3.c

#include <stdio.h>

//声明枚举数据类型
enum RETURN {RETURN_OK, RETURN_FAILED};

// 对枚举类型取别名
typrdef enum RETURN return_t;

/*定义检测函数*/
return_t check(int a) 
{  //函数的返回值是一个枚举值 
	if(a != 0) {
		printf("表示成功了\n");
		return RETURN_OK;   //表示成功了
	} else {
		printf("表示失败了\n")
		return RETURN_FAILED; // 表示失败了
}
}

int main(void)
{
	printf("%d\n", chenk(0));
	printf("%d\n", check(1))
	return 0;
}
结论:任何程序员只要看到RETURN_OK,RETURN_FAILED就知道具体含义是成功还是失败了,提高了代码可读性
参考代码:enum2.c
/*枚举经典案例*/

#include <stdio.h>
/*声明枚举数据类型*/
enum RETURN {RETURN_OK, RETURN_FAILED};
/*对枚举类型取别名*/
typedef enum RETURN return_t;

/*定义检测变量值的函数*/
return_t check(int a)
{
    if (a != 0) {
        printf("表示成功了\n");
        return RETURN_OK;
    } else {
        printf("表示失败了\n");
        return RETURN_FAILED;
    }
}

int main(void)
{
    printf("%d\n", check(0));
    printf("%d\n", check(1));
    return 0;
}
参考代码:enum3.c
/*经典案例#define宏实现*/
#include <stdio.h>
/*定义宏*/
#define RETURN_OK 0;
#define RETURN_FAILED 1;
/*定义检测变量值的函数*/
int check(int a)
{
    if (a != 0) {
        printf("表示成功了\n");
        return RETURN_OK;
    } else {
        printf("表示失败了\n");
        return RETURN_FAILED;
    }
}

int main(void)
{
    printf("%d\n", check(0));
    printf("%d\n", check(1));
    return 0;
}

第十课:函数指针(核心中的核心)

1. 回顾指针函数概念

本质是一个韩式,只不过它的返回值是一个指针而已

例如:

int *add(int a, int b)...

2. 切记切记

函数名就是整个函数的首地址

int add(int a, int b) {
	return a + b;
}
此函数在内存中的首地址就是add函数名

3. 函数指针概念

本质是程序员自己定义的新的数据类型(跟结构体,联合体,枚举一样,都是自己声明,自己定义)

用它定义的变量保存只能保存函数的首地址

4. 函数指针数据类型声明的语法:

返回值数据类型(*函数指针数据类型名) (形参表);

注意:声明的代码对于大型程序要写在头文件中

​ 建议用typedef对函数指针数据类型取别名

例如

int (*pfunction) (int a, int b);   // 这一堆代码就是一种函数指针数据类型,把这一堆当成int使用
								  // 书写起来太麻烦了,建议取别名
建议取别名:
typedef int (*pfunc_t) (int a, int b);  // 对函数指针取别名叫pfunc_t,以后直接拿着别名定义变量
注意:声明时,形参的变量名可以不用写

5. 函数指针变量定义的语法格式

函数指针数据类型/别名 函数指针变量
例如:
pfunc_t pfunc;  // 定义函数指针变量,此变量将来保存一个函数的地址
				// 所以此变量永远也只分配4字节内存

6. 函数指针变量的初始化

pfunc_t pfunc = add; // 定义初始化函数指针变量pfunc,并且保存add函数的首地址,简称指向add函数

7. 通过函数指针变量来间接访问指向的函数,调用函数,语法格式

函数指针变量名(实参表)

例如:
pfunc(100, 200);  //本质就是调用add函数,并且给add函数传递100和200

8. 切记

函数指针数据类型指定的函数返回值类型,形参表务必要和指向的函数的返回值和形参表类型保持一致

例如:

typedef int (*pfunc_t) (int a, int b);  // 它要求返回值和形参表都是int类型
所以将来指向的函数:例如:
int add(int a, int b)....它的返回值和形参表也必须是int类型
参考代码:pfunction.c
/*函数指针演示*/
#include <stdio.h>
/*声明函数指针数据类型,并且取别名*/
typedef int (*pfunc_t)(int, int);
/*定义加法和减法函数*/
int add(int a, int b)
{
    return a + b;
}

int sub(int a, int b)
{
    return a - b;
}

int main(void)
{
    int ret = 0;
    /*定义函数指针并且初始化为空指针*/
    pfunc_t pfunc = NULL;  // pfunc_t=数据类型  pfunc=指针名
    // 指向add函数
    pfunc = add;
    // 利用函数指针调用add函数
    ret = pfunc(100, 200);
    // ret = add(100, 200);   // 也能通过编译,并执行出结果,但是不知道有没有危害
    printf("%d\n", ret);

    // 指向sub函数
    pfunc = sub;
    // 利用函数指针调用add函数
    ret = pfunc(100, 200);
    printf("%d\n", ret);
    return 0;
}

9. 回调函数:

一个函数可以被当成参数传递给别的函数,这个函数的参数必须是函数指针

其中被传递的函数,称为回调函数,例如:add,sub

作用:将来写一个调用函数cal,然后完成一个功能,但是这个功能将来可能不能满足

​ 别的程序员的要求,此时让cal函数具备调用别的程序员实现的函数

​ 此时只需在cal函数的形参中添加一个函数指针,将来指向别的程序员

​ 的回调函数完成他想要的结果即可

参考代码:pfunction1.c
/*回调函数演示*/
#include <stdio.h>
/*声明函数指针数据类型并且取别名*/
typedef int (*pfunc_t)(int, int);

/*定义add函数和sub函数:又称回调函数:类似袋鼠宝宝*/
int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}
/*定义调用函数:类似袋鼠妈妈*/
int cal(int a, int b, pfunc_t pfunc) // 类似把袋鼠宝宝pfunc放在袋鼠妈妈cal的口袋里,放点食物a和b
{
    // 此函数的默认功能:做两个数相乘
    if (NULL == pfunc) {
        return a * b;
    } else {  // 说明默认功能不能满足需求,那么就调用别的程序员自己指定的回调函数完成它想要的功能
        return pfunc(a, b); // 调用回调函数并且给回调函数传递参数
    }
}
int main(void)
{
    int ret = 0;
    ret = cal(100, 200, add); // 调用cal函数,传递三个参数,最后一个参数回调函数add的首地址
    printf("%d\n", ret);
    ret = cal(100, 200, sub); // 调用cal函数,传递三个参数,最后一个参数回调函数sub的首地址
    printf("%d\n", ret);
    ret = cal(100, 200, NULL); // 调用cal函数,用此函数默认功能
    printf("%d\n", ret);
    return 0;
}

10. 函数指针经典代码演示

目的利用函数指针,循环,数组实现顺序调用一组函数

参考代码:pfunction2.c
/*函数指针,循环,数组结合使用,完成一组函数挨个调用*/
#include <stdio.h>
/*声明函数指针数据类型*/
typedef int (*pfunc_t)(int, int);  // 类比当成int类型
/*定义加法,减法,乘法,除法函数*/
int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}
int mul(int a, int b)
{
    return a * b;
}
int div(int a, int b)
{
    return a / b;
}

int main(void)
{
    /*1.定义初始化一个函数指针数组,每个元素是一个函数指针,也就是一个函数的地址*/
    pfunc_t array[] = {add, sub, mul, div, NULL}; //数组里的是地址 pfunc_t类比为int = int array[] = {},NULL如果不加虽然也可以运行出结果,但是有点问题,保险起见,还是加上
    printf("%d\n", sizeof(array) / sizeof(array[0]));  // 5

    /*挨个调用*/
    for(pfunc_t *pfunc = array; *pfunc; pfunc++)  // 看做int i = 0;pfunc指向array, *pfunc取出里面元素也就是函数,pfunc++取出下一个函数
    {
        int ret = (*pfunc)(200, 100);  // 先取出函数用(*pfunc),然后再传递实参  
        printf("ret = %d\n", ret);
    }
    return 0;
}

第十一课:多级指针(掌握到二级指针)

1. 回顾一级指针:

指向一个普通变量内存区域

例如:

int a = 250;
int *p = &a;  // p保存变量a的首地址,p指向a
printf("a的首地址%p\n, a的值是%d\n", p, *p);
// 修改值
*p = 520;

2. 二级指针概念:

指向一级指针变量的指针,也就是二级指针变量保存一级指针变量的首地址

3. 二级指针变量定义的语法格式

数据类型 **二级指针变量名 = 一级指针变量首地址;

例如:

int a = 250; 
int *p = &a;   // p指向a
// 定义二级指针保存一级指针变量p的首地址
int **pp = &p;  // pp 指向p
printf("p的首地址是%p,a的首地址是%p,a的变量的值是%d\n", pp, *pp, **pp);
// 修改变量a的值
**pp = 520;

立马浮现内存分布图:二级指针.png

在这里插入图片描述

参考代码:pppointer.c
/*二级指针演示*/
#include <stdio.h>

int main(void)
{
    int a = 250;
    int *p = &a;  // p指向a,p保存a的首地址
    int **pp = &p; // pp指向p,pp保存p的首地址
    // 通过二级指针查看
    printf("p的首地址是%p, a的首地址是%p, a的值是%d\n", pp, *pp, **pp);
    // 通过二级指针修改
    **pp = 520;
    printf("%d %d %d\n", a, *p, **pp);
    return 0;
}

目前看来,二级指针处理普通的变量多余,用一级指针即可拿下

4. 二级指针和字符串

经典笔试题:编写一个函数,实现两个字符串的交换

思路:

cahr *pa = "hello";
char *pb = "world";
目标:
pa指向->"world"; pb指向->"hello",指针互换

在这里插入图片描述

参考代码:swap.c
/*实现字符串交换:交换字符串首地址*/
#include <stdio.h>

/*无法交换*/
void swap(char *p1, char *p2)
{
    char *p3 = p1;
    p1 = p2;
    p2 = p3;
    printf("p1 = %s p2 = %s\n", p1, p2);
}

/*正确交换*/
void swap_ok(char **p1, char **p2)
{
    char *p3 = *p1; //p3 = hello
    *p1 = *p2;      // 修改一级指针pa的值  // pa = p1 = world
    *p2 = p3;       // 修改一级指针pb的值  // pb = p2 = hello
}

int main(void)
{
    char *pa = "hello";
    char *pb = "world";
    swap(pa, pb);
    printf("pa = %s, pb = %s\n", pa, pb);

    swap_ok(&pa, &pb);
    printf("pa = %s, pb = %s\n", pa, pb);
    return 0;
}

5. 二级指针和字符指针数组

5.1 回顾:字符指针数组

char *p[] = {"hello","world"};  // p[0]第0个元素的值是"hello"的首地址
								// p[1]第一个元素的值是"world"的首地址
或者
char *p1 = "hello";
char *p2 = "world";
char *p[] = {p1, p2}; // p[0] = p1, p[1] = p2
注意:给你一个字符串,实际给你的是字符串的首地址
显然这里p具有二级指针的意味,通过p获取的是每个字符串的首地址
所以可以定义一个二级字符指针变量来保存字符指针数组的首地址,例如:
char **pstr = p;
printf("%s %s %s %s %s %s\n", p[0], p[1], pstr[0], pstr[1], **(pstr[0]), *(pstr+1));

结论:
char **pstr等价于 char *p[元素个数];
参考代码:ppstring.c
/*二级指针和字符指针数组等价关系演示*/
#include <stdio.h>

int main(void)
{
    char *a[] = {"hello", "world"};
    char **p = a; // p指向a

    // 打印字符串
    printf("%s %s\n", a[0], a[1]);
    printf("%s %s\n", p[0], p[1]);
    printf("%s %s\n", *(a + 0), *(a + 1));
    printf("%s %s\n", *(p + 0), *(p + 1));

    // 通过p来打印"hello"字符串中每个字符
    printf("%c\n%c\n%c\n%c\n%c\n", *(*(p + 0) + 0), *(*(p + 0) + 1), *(*(p + 0) + 2),
           *(*(p + 0) + 3), *(*(p + 0) + 4));

    // 修改hello为hi
    *(p + 0) = "hi";
    printf("%s\n", *(p + 0));
    return 0;
}

6. 实际开发,主函数main必须这样写

  1. main函数的公式 (argc放的参数个数,argv放的参数内容(字符形式的))

    int main(int argc, char *argv[])
    等价于
    int main(int argc, char **argv)
    不要写成:int main(void)
    
  2. 切记:只要在命令行终端(不管是什么命令行)输入的任何内容,操作系统都把它们当成字符串来处理

    例如:

    ./helloworld 10 200
    

    结论:操作系统把这些输入的内容都当成了字符串

    "./helloworld"和"10"和"200"
    
  3. 问:main函数的形参agrc和argv到底是什么?

    答:argc:当运行程序时(例如:./helloworld),操作系统用argc这个参数来保存你命令行输入的参数个数

    ​ 例如:./helloworld,最后操作系统会自动给argc赋值为1,表示参数个数为1个

    ​ ./helloworld 100,最后操作系统会自动给argc赋值为2,表示参数个数位2个

    ​ argv:当运形程序时,操作系统用argv字符指针数组来保存输入的字符串首地址

    ​ 例如:./helloworld,最后操作系统自动给argv[0]赋值为"./helloworld"字符串的首地址

    ​ ./helloworld 100 200 ,最后操作系统自动给argv[0]赋值为"./helloworld"字符串的首地址

    ​ argv[1]赋值为"100"字符串的首地址

    ​ argv[2]赋值为"200"字符串的首地址

    结论:比如用户运行程序命令

    ./helloworld 100 200 300

    结果:
    argc = 4 参数为4个
    argv[0] = "./hellowprld"
    argv[1] = "100"
    argv[2] = "200"
    argv[3] = "300"
    

    参考代码:main.c

    /*main函数完整的编写框架*/
    /*运行
    ./main 100 200
    argc = 3
    argv[0] = "./main"首地址
    argc[1] = "100"的首地址
    argv[2] = "200"的首地址
    */
    #include <stdio.h>
    
    // int main(int argc, char **argv)
    int main(int argc, char *argv[])
    {
        for(int i = 0; i < argc; i++)
        {
            printf("argc = %d, argv[%d] = %s\n", argc, i, argv[i]);
        }
        return 0;
    }
    
    1. 程序运行时可以通过命令行传递参数,例如:“100”,“200”

      问:命令行输入的数字,最终该操作系统都处理成了字符串

      ​ 实际程序需要整型数字,如何将字符串转成整型函数呢?

      答:用strtoul标准库函数

      strtoul函数功能:就是实现字符串转整型数字 ,例如:“100” > “200”

      strtoul函数原型

      unsigned long int strtoul(const char *str, char **endptr, int base)
      

      str:传递要转换的字符串的首地址,例如:“100”

      endptr:一律给NULL

      base:指定那个进制转换

      ​ 1.如果base = 0,根据实际数字的形式进行转换

      ​ 例如:"100"根据10进制进行转换

      ​ "0x100"根据16进制进行转换0x100

      ​ 2.如果给base = 16,强制按照16进制进行转换

      ​ 例如:"100"根据16进制进行转换0x64

      ​ 3.如果base = 8,强制按照8进制进行转换

      ​ 例如:"100"根据16进制进行转换成0144

    2. 返回值:返回转换完成的整数

参考代码:main.c
/*main函数完整的编写框架*/
/*运行
./main 100 200
argc = 3
argv[0] = "./main"首地址
argc[1] = "100"的首地址
argv[2] = "200"的首地址
*/
#include <stdio.h>
#include <stdlib.h>
// int main(int argc, char **argv)
int main(int argc, char *argv[])
{
    for(int i = 0; i < argc; i++)
    {
        printf("argc = %d, argv[%d] = %s\n", argc, i, argv[i]);
    }
    printf("\n");
    if (argc != 3)
    {
        printf("请重新输入 %s <100><200>\n", argv[0]);
	return -1;
    }
    //将用户输入的字符串转整数
    int a = 0,b = 0;  // 将来保存用户输入的数字
    a = strtoul(argv[1], NULL, 0);   // 第一个参数时要转换的字符,第二个参数写NULL就行,第三个参数时几进制,0为根据实际转换输出
    b = strtoul(argv[2], NULL, 0);
    printf("a = %d, b = %d\n", a, b);
    return 0;
}


第十一课:结构体和函数指针的配合专项训练(实际产品开发比用)

参考代码:struct_pfunction.c

/*结构体和函数指针的配合*/
#include <stdio.h>
/*告诉gcc编译器,stu_t别名在下面的代码中实现*/
typedef struct student stu_t;
/*声明函数指针数据类型*/
typedef void (*pfunc_t)(stu_t *);
/*声明结构体*/
typedef struct student
{
    char name[30];
    int age;
    pfunc_t pfunc1;
    pfunc_t pfunc2;
} stu_t;
// 定义打印学生信息的函数
void show(stu_t *p)
{
    printf("%s %d\n", p->name, p->age);
}
//定义岁数加1函数
void grow(stu_t *p)
{
    p->age++;
}
int main(void)
{
    //定义初始化结构体变量描述学生信息
    stu_t student1 =
        {
            .name = "小明",
            .age = 18,
            .pfunc1 = show, // 指向show函数
            .pfunc2 = grow  // 指向grow函数
        };
    //调用student1学生的操作函数:show和grow
    student1.pfunc1(&student1); // 调用show函数
    student1.pfunc2(&student1); // 调用grow函数
    student1.pfunc1(&student1); //调用show函数
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值