指针与函数
指针作为函数的参数
- 函数有多个返回值时
- 地址传递
- 如果函数的参数是一个指针,函数希望传递给调用者一个信心,保证不会修改值,就可以加一个 const 修饰
指针最为函数的返回值
- 当然可以作为返回值.
- 函数中的数组作为返回值,如果是在函数内创建的数组,函数执行完后就会回收,返回的地址虽然可以访问,数据就不一定在了.
- 你返回的指针指向的变量一定要保证函数结束后,那个空间没被回收.
如果一定要返回一个指针,就将空间申请在堆区.
int* test(); { int* arr = calloc(3,sizof(int)); *arr = 10; *(arr+1) = 20; *(arr+2) = 30; return arr; } 调用者: int* arr = test();
调用者用完后一定要
free(arr);
- 注意:返回值可以返回局部变量的值,但不能返回局部变量的地址!
案例
返回一个1-7数字对应的英文星期几
char* getWeekDay(int day);
//如果返回值是字符串,那么类型就是 char 指针
{
switc(day)
{
case 1:
return "Monday";
}
}
另一种写法
//返回字符指针.
char* weekDay = "未知";
switch(day)
{
case 1:
weekDay = "Monday";
break;
}
return weekDay;
调用
char* str = getWeekDay(1);
//申请在常量区的空间是不会回收的,程序结束才会回收.
指向函数的指针
程序在运行的时候,会将程序加载到内存
- 程序中主要有代码/指令.
- 代码段中主要存储程序的代码
- 程序的代码就包括函数
- 既然函数要存储到内存中,就肯定需要空间,有空间就有地址,有地址就可以声明一个指向函数的指针!
那么调用函数就有了两种方式. - 直接用函数名
- 用指向函数的指针.声明语法(有点复杂)并不是任意函数都可以指向的
a. 只能指向没有返回值并且没有参数的函数
返回值类型(*指针名)([参数列表]);
void(*pFunction)();
表示声明了一个指向函数的指针,名字叫做 pFunction.
b.只能指向返回值为 int 类型,并且有两个整形的参数的函数.
int(*pfun)(int num1,int num2);
c.初始化
1. 取到符合指针条件的函数地址
2. 函数的名称就代表函数的地址.
printf("%p",test);
//千万不要加小括号,加了就是执行函数,拿到返回值.
3. 将地址赋给指针变量,直接将符合条件的函数的名称赋给这个指针.
void(*pfun)() = test;
//pFun指针就指向了 test 函数.
d.调用
//简写
pFunc();
//原样.
(*pFunc)();
e.小技巧
拷贝函数头
int test(int num1,int num2);
删除函数名,用小括弧代替,里面写上*加指针名
int (*pFunction)(int num1,int num2);
后面加赋值符号,将原来的函数名(函数的地址)赋值给这个指针
int (*pFunction)(int num1,int num2) = getSum;
然后直接写指针名加括号调用
int num = pFunction(10,20);
结构体
存一个人的年龄你会怎么存?
int age = 18;
占用了4个字节,浪费
一个人的年龄0-200之间,没有负数,一个字节就够了
unsigned char age = 18;
保存一个人的身高.一个班级的平均分.
//一个变量就是来存储一个数据描述这个数据的.
- 如果让你保存一个学生呢?
使用我们之前任何一个变量都不行
1. 姓名 字符串
2. 年龄 int
3. 性别 char
4. 成绩 double
我们要表示一个学生,就需要多个普通的变量的和起来描述.
- 怎么合起来?
exa: 数组
数组要求元素的类型一致,所以拜拜
exa: 一种有几种普通变量合成的一个大变量.
没有这种类型的变量,但是我们可以自己定义这样的数据类型.
指定这个数据类型的变量是由哪些小变量合成的
使用结构体来创建新类型的数据
struct 新类型名称
{
数据类型1 变量名称1
数据类型2 变量名称2
数据类型3 变量名称3
}创建了一个数据类型,由 char 指针 int 变量,float 联合而成.
struct Student
{
char* name;
int age;
int score
float height;
};
创建了类型,保存在代码段.刚刚只是创建了一个类型,指定了由哪些数据类型组成,还没有变量.
再声明刚才创建的结构体类型的变量
struct 结构体名称 变量名;
struct Student stu;
//这个结构体大变量是由结构里规定的小变量组合而成的.
stu 结构体变量的类型是 struct Student.结构体变量的初始化(赋值)
stu.name = “jack”;
stu.score = 100;
stu.age = 17;
stu.height = 189.8;
结构体名称.成员名 = 数据;
printf(“姓名:%s 年龄:%d 成绩:%d 身高:%.2f\n”,stu.name,stu.age,stu.score,stu.height);- 总结,什么时候定义结构体
- 我们要保存一个数据, 但是发现这个数据是一个大数据,由其他小数据联合起来组成的
- 保存一个坐标点
- 一个图片的长和宽等等,使用数组每个变量没有名称,结构体更容易让人理解
注意的几个小问题
- 一定要先使用结构体定义新的类型,然后才可以根据这个类型声明这个类型的变量.
结构体是一个变量.所以可以批量声明
struct Student xiaoHua,jin,meiMei,liLei
- 结构体的命名规范,要求每个单词首字母大写
- 声明结构体同时声明变量
struct Computer
{
char* cpumodel;
int menSize;
char* brand;
} iMac,lenvol,hp,dell;- 匿名结构体
struct
{
char* barnd;
char* color;
int price;
}fengshan1;
//只能在声明结构体的同时就命名
//声明后不能单独的声明这个结构体的变量 - 我们要保存一个数据, 但是发现这个数据是一个大数据,由其他小数据联合起来组成的
结构体变量的初始化
刚才我们是先声明变量再使用点. 一个个的为成员赋值,你是不是觉得很麻烦?
在声明结构体变量的同时,怎么为结构体变量的成员初始化呢?
//最常用的
struct Student xiaoHua = {“小花”,18,89}; *
//或者初始化一部分
struct Student liLei = {“李雷”};
//还可以指定成员初始化
struct Student jim = {.name = “基姆”,.age = 17,.score = 100};结构体变量的初始值
- 声明一个结构体变量,如果没有为成员赋值,成员就是垃圾值.
- 只要在声明结构体变量的同时,初始化一个成员,其他成员的值就是0;
结构体的作用域.
- 结构体是定义在函数内部的,那么结构体类型只能在函数的内部使用
- 如果希望所有函数都可以使用,就定义在最顶上
- 一般情况下我们的结构体类型都是定义在函数的外面的,让所有的函数来用
结构体之间的相互赋值
相同结构体类型的变量之间绝对是可以相互赋值的
struct Student xiaoMing = {“小明,19,100};
struct Student xiaoHua = xiaoMing;- 赋值原理
将源结构体当中的成员变量的值拷贝一份给目标结构体的变量.
结构体变量赋值是值传递
结构体数组
如果你有5个学生的信息你会怎么做?
声明5个结构体?
struct Student s1 = {"小明1",16,56};
struct Student s2 = {"小明2",18,77};
struct Student s3 = {"小明3",19,98};
struct Student s4 = {"小明4",16,67};
struct Student s5 = {"小明5",17,100};
其实可以用结构体数组来管理
声明:
struct 结构体类型名称 数组名称[数组长度];
struct Student students[5];
表示我们声明了一个长度为5的结构体数组
数组名叫 students
数组的元素的类型的 struct Student
结构体数组
如果你有5个学生的信息你会怎么做?
声明5个结构体?
struct Student s1 = {"小明1",16,56};
struct Student s2 = {"小明2",18,77};
struct Student s3 = {"小明3",19,98};
struct Student s4 = {"小明4",16,67};
struct Student s5 = {"小明5",17,100};
其实可以用结构体数组来管理
声明:
struct 结构体类型名称 数组名称[数组长度];- 表示我们声明了一个长度为5的结构体数组
- 数组名叫 students
- 数组的元素的类型的 struct Student
struct Student students[5];
students[0] = s1;
students[1] = s2;
students[2] = s3;
students[3] = s4;
students[4] = s5;
遍历
一个 for 循环就可以搞定了
for(int i = 0;i < 5;i++)
{
printf(“姓名:%s 年龄:%d 成绩:%d\n”,
students[i].name,
students[i].age,
students[i].score
);结构体数组的初始化
- 刚才那种用下标一个个初始化的方式仍然是很傻的方式.(以下的方法可以认为是给部分指定的元素初始化)
struct Student students[5];
students[0] = (struct Student){“小明1”,16,56};
students[1] = (struct Student){“小明2”,18,100};
students[2] = (struct Student){“小明3”,19,10};
students[3] = (struct Student){“小明4”,21,100};
students[4] = (struct Student){“小明5”,13,3};
注意: 当我们为结构体数组的元素赋值的时候.如果直接使用大括弧来初始化.
就必须要前面加1个小括弧,来告诉编译器我们的给的数据类型.- 在声明结构体数组的同时就初始化(按顺序初始化)
//5 可以省略
struct Student students[5] =
{
{“小明1”,16,56},
{“小明2”,18,100},
{“小明3”,19,10},
{“小明4”,21,100},
{“小明5”,13,3}
};计算长度
怎么计算总长度?
int len = sizeof(students)
len == 80
在计算每个元素的字节数,用总长度一除
int len = sizeof(students)/sizeof(struct Student);
//students是数组名,student 是结构体类型名称
结构体指针
struct student
{
char* name;
int age;
int score;
};
struct Student xiaoMing = {"小明",18,100};
xiaoMing 是一个变量.类型是 sturct Student 类型的
既然是变量就有地址,有地址就有指针.
格式
struct 结构体类型名称* 指针名;
struct Student* pStu;
//声明了一个能指向 struct Student 类型的指针变量.
初始化
取出结构体变量的地址
&xiaoMing;
赋值给指针变量
pStu = &xiaoMing;
struct Student* pStu = &xiaoMing;
操作
(*结构体指针名).成员
(*pStu).name = "jack";
(*pStu).age = 18;
(*pStu).score = 99;
第二种方式
pStu->name = "jack";
pStu->age = 18;
代表把18 赋给 pStu 指向的 age 成员.
结构体的嵌套
当我们在为结构体定义成员的时候. 发现某个成员也是1个大数据 需要其他的几个小变量合起来描述,那么这个时候你就可以再定义1个数据类型.来表示这个类型.
struct Date
{
int year;
int month;
int day;
};
//在 Person 结构体中加入一个 Date 结构体保存出生年月日.也可以加入农历出生年月日.
struct Person
{
char* name;
int age;
double money;
struct Date birthday;
};
struct Person xiaoMing = {"曾凡怡",22,100000000.0,{1994,2,23}};
printf("姓名:%s--年龄%d--财产:%lf--生日:%d--%d--%d",
xiaoMing.name,
xiaoMing.age,
xiaoMing.money,
xiaoMing.birthday.year,
xiaoMing.birthday.month,
xiaoMing.birthday.day
);
结构体作为函数的参数
- 结构图是一种数据类型,当然也可以作为函数的参数和返回值.
结构体作为参数传值是值传递
struct Student
{
char *name;
int age;
int score;
};
void panDuanXueSheng(struct Student stu)
{
if(stu,score >= 60)
{
printf(“恭喜%s 你及格了.\n”,stu.name);
}
else
{
printf(“抱歉%s 你落榜了”,stu.name);
}
}
这个函数接收的是值,函数内部如果想更改实参变量的值是不行的.如果你就是希望函数的内部可以修改实参的值,就使用指针
void jiaFen(struct Student* stu)
{
stu->score = 100;
}结构体作为返回值
struct getAStudent()
{
struct Student s1 = {“rose”,21,100};
return s1;
}返回一个结构体指针
include
枚举
限定一个变量中只能给指定的几个值,除此之外都不行.C 语言默认没有提供限定取值类型的变量,我们就自己定义一个,枚举就支持我们新创建一个这种类型.
enum 新类型的名称
{
限定取值1,限定取值2,限定取值3,......
};
enum Direction
{
East,
South,
West,
North
};
1. 表示新创建了一个数据类型,名字叫做 enum Direction.
2. 可以声明这个类型的变量.
3. 这个变量中就只能存储这其中指定的任意一个.
声明
enum 枚举类型名称 变量名;
enum Direction dir;- 表示声明了一个变量,名字叫 dir
- 类型是 enum Direction.
- 只能存储这个枚举类型限定的取值之一.
enum Direction dir = East;
注意
- 枚举的作用域在定义的函数内部使用,希望所有的都是用就定义在最顶部.
- 每一个 枚举值/枚举项 都有一个对应的整形的数,默认从0开始,依次递增.%d 打印就看得到
这个枚举有多大?
int len = sizeof(dir); 无论是什么类型的枚举变量都是4个字节.
枚举变量当中(dir),真正存的就是对应的整形得数.printf(“%d\n”,dir);
- 所以也可以给枚举变量赋值一个整形的数据…
- 虽然我们可以给整数,但我们并不会这么做.代码的可读性会很差.
- 给1和 South 完全等价
- 手动指定对应的数.
enum Direction
{
East = 10,
South = 20,
West = 30,
North = 40
};
East 对应的整形的数就是10.一些规范
- 枚举类型的命名规范:
首字母大写.每个单词的首字母也大写
- 枚举类型的命名规范:
- 枚举值的命名规范:
以枚举的类型开头.这样你敲出前面几个字符就可以出现提示,枚举中有哪些值枚举.
typedef
作用:
- 为一个已经存在的数据类型取一个别名.
- 如果我们想要使用这个类型,就直接使用别名.
语法格式:
typedef 已经存在的数据类型 别名;
typedef int itheima;
为 int 数据类型取了一个别名,叫做 itheima
int == itheima;//这两个名字现在就完全等价了.
typedef char* string;
string name = "rose";
//一秒钟让你的 C 语言有 string 类型
我们前面了解的 size_t 其实就是 typedef 定义的 unsigned long
3. 什么时候用?
当数据类型很长的时候 unsigned long long int 这种,定义以后用起来就很方便了.
4. 还有呢?
struct Student 好长好难写. typedef 他,写在外面最顶上. typedef struct Student Student;
5. 先声明再重命名,也可以声明的同时就重命名.
typedef struct Student//这个 Student 是结构体名称
{
char* name;
int age;
int score;
}Student;//这个 Student 是重命名后的数据类型名称.
6. 声明匿名结构体的同时就为结构体取一个短别名 *
上面的第一个 Student 删掉,就是一个匿名结构体,后面一个数据类型的 Student 就成了这个匿名结构体的短名.***
7. typedef 为枚举取一个短别名
enum Direction
{
DirectionEast
};
type enum Direction Direction;
声明同时改名
type enum Direction
{
DirectionEast
}Direction;
//其实....使用上面这种方式和匿名结构体一样,第一个 Direction 也不用写了.