C语言学习第十二天

argument:参数
count:个数
argc:参数个数
argv:参数信息
malloc:分配
free:释放
size:大小
addr:地址
get:获取
info:信息

回顾:
1.Makefile
制定编译规则,让gcc根据编译规则进行编译程序
语法:
目标:依赖1 依赖2 …依赖N
(TAB键)编译命令1

(TAB键)编译命令N
make命令使用
工作流程
小技巧:
%.o:%.c
gcc -c -o $@ $<

2.复合类型:结构体
特点:包含一堆变量,数据类型可以不一致,特别适合描述事物的属性
结构体也是一种数据类型,只是是程序员自己定义的,玩法和int一致的
结构体声明定义使用方式:
//声明
struct {
成员;
}A;
struct A {
成员
};
typedef struct A {
成员
}A_t;
typedef struct A A_t;

//定义
struct A a;
A_t a;

//初始化
A_t a = {按照顺序全部初始化};
A_t a = {.成员名 = 初始化值,…}

结构体变量成员访问:例如:a.成员名
结构体指针变量成员访问:例如:p = &a; p->成员名

结构体嵌套:成员还是一个结构体
嵌套结构体变量:A_t a; a.b.成员名
嵌套结构体指针变量:B_t b; A_t a; a.p = &b; a.p->成员

函数的形参是结构体变量(不要这么做)或者结构体指针变量

3.复合类型:联合体
用法和结构体一模一样,关键字:union
所有成员公用一块内存
记住:经典笔试题


4.复合类型:枚举
4.1.枚举的本质就是一堆整数的集合,列表,就是给整数取了个别名,提高代码的可读性
枚举值默认是从0开始,后面的成员依次加1
例如:250(不具体)—通过枚举的方式----->坏蛋BAD EGG(250的别名)
0-------通过枚举的方式------------->红色RED(0的别名)
1-------通过枚举的方式------------->绿色GREEN(1的别名)
2-------通过枚举的方式------------->蓝色BLUE(2的别名)
4.2.声明枚举数据类型的语法:
enum 枚举数据类型名 {枚举值};
例如:enum COLOR {RED, GREEN, BLUE};
结果:RED=0,GREEN=1,BLUE=2 等价于给0,1,2取别名
printf("%d %d %d\n", RED, GREEN, BLUE); //0,1,2
例如:
enum COLOR {RED, GREEN=250, BLUE};
结果:RED=0,GREEN=250,BLUE=251 等价于给0,250,251取别名
printf("%d %d %d\n", RED, GREEN, BLUE); //0,250,251
结论:将来程序中再描述红,绿,蓝三种颜色,就不需要用0,1,2(代码可读性非常差)
用RED, GREEN,BLUE替换0,1,2

4.3.枚举的经典使用代码方式(操作系统核心代码片段)
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", check(1));
return 0;
}
保存退出
结论:程序员很难直到0和1谁是成功谁是失败!代码可读性非常差
提高代码可读性可以采用枚举方法,也就是给0和1取别名:RETURN_OK,RETURN_BAD
改进以后的代码:
vim enum3.c 添加
#include <stdio.h>
//定义枚举数据类型
enum RETURN {RETURN_OK, RETURN_BAD};
//对枚举数据类型取别名
typedef enum RETURN return_t;
/定义检测函数/
return_t check(int a)
{
if(a != 0) {
printf(“表示成功了.\n”);
return RETURN_OK; //表示成功
} else {
printf(“表示失败了.\n”);
return RETURN_BAD; //表示失败
}
}
int main(void){
printf("%d\n", check(1));
printf("%d\n", check(0));
return 0;
}

5.函数指针(核心中的核心)
5.1.明确相关概念
a)指针函数:就是一个函数,只是它的返回值是一个指针
例如:int *add(int a, int b)
b)函数名就是整个函数里面代码的首地址,简称函数的首地址
int add(int a, int b)
{
printf("%d\n", a);
printf("%d\n", b);
return a + b;
}
add这个函数名就是整个函数add的首地址,就是三条语句的首地址,等于第一条语句
printf函数所在内存的首地址

5.2.函数指针概念:本质就是一种程序员自己定义的数据类型(跟int,结构体数据类型一样的)
它保存着一个函数的地址
5.3.函数指针数据类型声明的语法:(不会分配内存并且大型程序中写头文件)
返回值数据类型 (*函数指针名)(形参表);
例如:
int (*pfunc)(int a, int b); //pfunc就是一种数据类型,当成int型来使用
或者
typedef int (*pfunc_t)(int a, int b); //对函数指针取别名叫pfunc_t,以后都这么用

5.4.函数指针变量定义语法格式:函数指针名 函数指针变量;
例如:pfunc_t pfunc; //pfunc就是一个函数指针变量,将来保存函数的地址

5.5.函数指针变量的初始化
pfunc_t pfunc = add; //定义并且初始化函数指针变量,指向add函数
等价于
int (*pfunc)(int a, int b) = add; //pfunc指向add函数,此种写法极其繁琐
5.6.通过函数指针变量来访问指向的函数,通过函数指针变量来调用指向的函数
语法格式:函数指针变量名(实参表);

5.7.函数指针总结
a)函数指针建议用typedef取别名
例如:pfunc_t
b)函数指针的返回值和形参表务必要和指向的函数的返回值和形参表一致
c)回调函数:一个函数可以被当成参数传递给别的函数,这个函数的参数必须是函数指针
来保存回调函数的地址(例如:add)

5.8.函数指针经典代码演示:
vim pfunction3.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 mul(int a, int b){return a*b;}
int div(int a, int b) {return a/b;}
int main(void)
{
	//目的:把所有的函数调用一遍
	//1.定义函数指针数组,每个元素是一个函数指针,是一个函数的地址
	pfunc_t pfunc_array[] = {add, sub, mul, div, NULL};
	
	//2.挨个调用
                 for(pfunc_t *pfunc = pfunc_array; *pfunc; pfunc++) {
		int ret = (*pfunc)(100, 200);
		printf("ret = %d\n", ret);
	}
	return 0;
}

结论:将来如果想让main函数调用一堆的函数,只需将函数放到数组中即可,将来
for循环自动帮你挨个调用!

/*回调函数演示*/
#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)
{
    return pfunc(a, b); //调用pfunc指向的函数 
}

int main(void)
{
    int ret = 0;
    ret = cal(100, 200, add); //add称之为回调函数
    printf("ret = %d\n", ret);
    ret = cal(100, 200, sub); //sub称之为回调函数
    printf("ret = %d\n", ret);
    return 0;
}

6.多级指针(掌握到二级指针)
6.1.回顾一级指针:指向一个普通变量的内存区域
例如:int a = 250;
int *p = &a; //p保存着a变量的首地址
printf(“a的首地址%p, a的值是%d\n”, p, *p);

6.2.二级指针定义:指向一级指针的指针,也就是存放着一级指针变量的地址
6.3.定义二级指针变量的语法格式:数据类型 **二级指针变量名 = 一级指针的地址;
例如:
int a = 250;
int *p = &a;
int **pp = &p;
//读查看
printf(“p的地址是%p, a的地址是%p, a的变量是%d”, pp, *pp, **pp);
//写修改
**pp = 350;
目前来看,二级指针处理普通的变量多余, 用一级指针即可拿下!
在这里插入图片描述

6.4.二级指针和字符串的那点事
经典的笔试题:编写一个函数实现两个字符串的交互
例如:
char *pa = “hello”;
char *pb = “world”;
目标:pa->“world”, pb->“hello”,指针互换
注意:通过pa和pb指针是无法修改字符串的值

/*字符串的交换*/
#include <stdio.h>
//定义交换函数swap和swap1
void swap(char *p1, char *p2)
{
    char *p3 = p1;
    p1 = p2;
    p2 = p3;
    printf("swap:%s  %s\n", p1, p2);
}
void swap1(char **p1, char **p2)
{
    char *p3 = *p1;
    *p1 = *p2; //修改一级指针pa的值
    *p2 = p3; //修改一级指针pb的值
}
int main(void)
{
    char *pa = "hello";
    char *pb = "world";
    swap(pa, pb); //没有交换
    printf("%s %s\n", pa, pb);
    swap1(&pa, &pb); //交换
    printf("%s %s\n", pa, pb);
    return 0;
}

6.5.二级指针和字符指针数组的那点事
a)回顾:字符指针数组形式
char *p[] = {“hello”, “world”}; //p[0]保存字符串"hello"的首地址
//p[1]保存字符串"world"的首地址
或者
char *p1 = “hello”;
char *p2 = “world”;
char *p[] = {p1, p2};
显然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[元素个数]

/*二级指针和字符指针数组的那点事*/
#include <stdio.h>

int main(void)
{
    char *a[] = {"hello", "world"};
    char **p = a; //p指向a
    printf("%s %s %s %s %s %s\n",
            a[0], a[1], *(p+0), *(p+1), p[0], p[1]);
    printf("%c %c %c %c %c\n", 
            **p, *(*(p)+1), *(*(p)+2), *(*(p)+3), *(*(p)+4));
    printf("%c %c\n", 
            *(p[1]+0), *(p[1]+1));
    return 0;
}  

6.6.实际开发,产品代码的主函数main将来必须这么写,否则遭鄙视!
a)main函数的公式:
int main(int argc, char *argv[])
或者:
int main(int argc, char **argv)
不要写:int main(void)

b)切记:只要在命令行终端(不管是什么命令行)输入的任何内容,计算机都当成字符串处理
例如:./helloworld 100 200 ->实际计算机存储他们都是按照字符串存储
内存中最终存储字符串"./helloworld",字符串"100",字符串"200"

c)问:main函数的argc,argv到底是什么鬼呢?
答:argc, argv功能是当运行程序时,可以在命令行终端上给程序传递数值
实际上输入的数值都是按照字符串处理了
argc:操作系统会帮你记录命令行输入的命令参数个数
argv:操作系统会用字符指针数组记录命令行输入的字符串内容
例如:启动helloworld程序,并且后面跟两个数字100和200
运行命令:./helloworld 100 200
结果是:
argc = 3
argv[0] = “./helloworld” //argv[0]指针指向字符串"./helloworld"
argv[1] = “100” //argv[1]指向字符串"100"
argv[2] = “200” //argv[1]指向字符串"200"

d)问:运行程序时,可以跟数字,但是数字最终都是以argv字符串形式存在
程序如何将一个字符串转对应的数字呢
例如:“100” 转成 100, "200"转200
答:利用大名鼎鼎的游哥最爱的函数:strtoul(大神编写好的函数,咱直接用即可)
函数声明如下:
unsigned long int strtoul(const char *str, char **endptr, int base)
函数功能:将一个数字的字符串形式转成数字形式
例如:“100” 转成 100, "200"转200
形参:
str:传递要转换的字符串的首地址,例如:“100”
endptr:一律给NULL
base:指定按那个进制进行转换
1.如果给0,根据实际的数字书写形式进行转换:
例如:“100”-> 100 //10进制
“0100”->0100 //8进制
“0x100”->0x100 //16进制
2.如果给16,强制转换成16进制
例如:“100”->0x64
3.如果给8,强制转换成8进制
例如:“100” ->0144
返回值:返回转换以后的数字
例如:
argv[1] = “100”
int a = strtoul(argv[1], NULL, 0);
printf(“a = %d\n”, a); //a = 100


7.malloc和free标准库函数(大神写好的,咱直接用)
7.1.回顾:C语言分配内存方法三种:
1.定义变量:缺陷:不能分配大量内存
2.定义数组 : 缺陷:跟数据类型相关,类型相同,作用域一说,可以做到大量内存分配
局部数组:访问的范围有限
全局数组:存在乱序访问的问题,只能添加互斥保护,而保护会降低运行效率
3.结构体(联合体):可以有数组成员来实现大量内存访问,同样由数组的缺陷问题
问:如何做到大量分配内存,并且分配的内存跟数据类型无关,并且没有全局和局部作用域的 限制(想用内存了,随时随地分配,不用内存了,随时随地释放)
答:利用大名鼎鼎的malloc和free函数

7.2.详解malloc和free函数
a)malloc函数的原型:
void *malloc(unsigned long size);
功能:动态(随时随地的)连续分配内存
它分配的内存,只要不调用free函数就不会释放
并且分配的内存数据都是随机数
形参:
size:指定要分配的内存大小,单位是字节
返回值:返回分配的内存的首地址,注意要做强制类型转换
如果分配内存失败,返回NULL
头文件:为了使用此函数必须添加头文件:#include <stdlib.h>
参考代码:
int *p = (int *)malloc(8); //p指向连续分配的8个字节内存的首地址
if(NULL == p) {
printf(“分配内存失败”);
return -1;
} else {
printf(“分配内存成功.\n”);
*(p+0) = 2;
*(p+1) = 3;
printf("%d %d\n", *p, *(p+1));
}
或者:
void *p = malloc(8); //p指向连续分配的8个字节内存的首地址
if(NULL == p) {
printf(“分配内存失败”);
return -1;
} else {
printf(“分配内存成功.\n”);
*(int *)(p+0) = 2;
*(int *)(p+4) = 3;
printf("%d %d\n", *(int *)(p+0), *(int *)(p+4));
}

b)free函数原型
void free(void *p);
功能:调用此函数即可随时释放malloc分配的内存,将内存资源归还给操作系统
形参:
p:传递malloc分配的内存的首地址
参考代码:
//释放上面malloc分配的内存
free§;
p = NULL; //务必养成好习惯,否则p就成野指针了

c)memset函数原型:
void memset(void *p, int data, int len);
函数功能: 设置内存数据
参数:
p:传递要设置的内存的首地址
data:传递要指定的数据
len:传递要设置的内存大小
例如: memset(p, 0, 8); //将以p为其实的内存,连续8字节都设置为0

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值