1、将今天所敲课堂代码,自己手动实现一遍,并详细注释
一、值传递、地址传递、值返回、地址返回
1、左值:既能放到等号右边又能放到等号左边值,有地址空间
如:变量、堆区申请的空间、地址返回的函数返回值
右值:只能放到等号右边的值,没有地址空间
如:常量、临时值、表达式的结果、值返回的函数返回值
2、值传递:普通变量作为函数参数传递是单向的值传递,只是将实参的值复制一份给形参变量,形参的改变不会影响实参的值,因为所在内存空间不同
void swap(int m, int n)
{
int temp;
temp = m; //此时进行三杯水交换的是m和n
m = n;
n = temp;
printf("m=%d, n=%d\n", m, n); //34 23
}
int main()
{
int a=23;
int b=34;
swap(a,b); //函数调用把值给形参,但a和b并没有进行交换
printf("a=%d,b=%d\n",a,b) //23 34
}
如果传递的是地址,被调函数使用指针接收,如果在被调函数中,没有更改指针指向空间中的内容,只改变指向,依然是值传递
void fun(int *p, int *q)
{
int *temp;
temp = p; //此时p和q所指向的地址进行交换
p = q;
q = temp;
printf("*p=%d,*q=%d\n",*p,*q); //34 23
}
int main()
{
int a=23;
int b=34;
fun(&a,&b); //函数调用把地址给形参,但a和b并没有进行交换
printf("a=%d,b=%d\n",a,b); //23 34
return 0;
}
3、地址传递:指针、数组名作为函数参数传递,是地址传递,需要在被调函数中更改指针指向空间中的内容,形参内容的改变,实参也跟着改变
要求:主调函数中传递地址,被调函数中使用指针变量接收,被调函数中更改指针指向的内容
void gun(int *p, int *q)
{
int temp;
temp = *p; //此时交换的是p和q所指向地址中的值
*p = *q;
*q = temp;
printf("*p=%d,*q=%d\n",*p,*q); //34 23
}
int main()
{
int a=23;
int b=34;
gun(&a,&b);
printf("a=%d,b=%d\n",a,b); //34 23
return 0;
}
4、值返回:普通变量通过函数返回值进行返回是单向的值返回,在主调函数中,该函数的返回值只能作为右值使用,不能被重新赋值
int hun() //int k = 100;
{
int value = 666;
return value;
}
int main(int argc, const char *argv[])
{
int ret = hun(); //值返回的函数返回值只能是右值
printf("hun() = %d\n", hun()); //666
return 0;
}
5、地址返回:需要返回生命周期比较长的变量地址(全局变量、静态局部变量、堆区申请空间、主调函数地址传递的空间),该函数的返回值是一个左值,可以直接使用,也可以被重新赋值,被重新赋值后,被调函数中该空间中的内容也跟着改变
int *iun()
{
static int value = 999; //静态变量虽然在函数体内定义,但是不占函数的内存空间
return &value;//(int *) //返回静态局部变量的地址
}
int main()
{
int *ptr = iun(); //地址返回的结果可以作为右值
*iun() = 555; //地址返回的结果可以作为左值
printf("*iun() = %d\n", *iun()); //555
printf("*ptr = %d\n", *ptr); //555
return 0;
}
二、内存分区
#include
int m; //未初始化的全局变量,在全局区的.bss段
int n = 520; //已初始化的全局变量,在全局区的.data段
static int k; //未初始化的静态变量,在全局区的.bss段
static int l = 666; //已初始化的静态变量,在全局区的.data段
char arr[100] = "hello world"; //arr数组在全局取的.data段,而"hello world"在.ro段
char *p = "hello"; //指针在.data段,而“hello”在.ro段
int main(int argc, const char *argv[])
{
double b = 999.0; //局部变量在栈区申请
int a; //局部变量,在栈区申请,初始值为随机值
//printf("&a = %p, &b = %p\n", &a, &b);
static int c; //静态局部变量,在全局区的.bss段申请
static int d = 520; //静态局部变量,在全局区的.data段
char *q = "nihao"; //q在栈区申请的8字节,但是"nihao"在全局区的.ro段
char e[100] = "hello world"; //数组在栈区申请,"hello world"在全局区的.ro段
int *ptr = (int *)malloc(sizeof(int)); //ptr是在栈区,而申请的空间在堆区
return 0;
}
三、动态内存分配和回收(malloc、free)
C语言中可以使用malloc和free来对堆区空间进行操作
#include //函数头文件
void *malloc(size_t size);
/*功能:允许程序员手动从堆区空间申请内存
参数:要申请的空间大小,以字节为单位,一般格式为 sizeof(类型名)*n
返回值:是一个万能指针,可以使用强制类型转化为自己想要的类型的指针,如果申请成功,则将堆区申请的空间地址返回,如果申请失败,返回NULL
单个空间内存的申请:数据类型 *指针名 = (数据类型*)malloc(sizeof(数据类型)));
连续内存的申请: 数据类型 *指针名 = (数据类型*)malloc(sizeof(数据类型)*n));*/
int *p1=(int *)malloc(sizeof(int))
int *p1=(int *)malloc(sizeof(int)*5) //类似于数组
void free(void *ptr);
/*功能:释放程序员从堆区申请的空间
参数:要释放的空间首地址
返回值:无*/
//释放:free(指针名);
free(p);
p=NULL;
四、类型重定义
1、使用格式
/*typedef 数据类型名 新名;
举个例子:*/
typedef unsigned long int uint64;
typedef unsigned int uint32;
typedef unsigned short int uint16;
2、所学类型
使用变量定义类型
int a; //定义普通变量
int *p; //定义指针类型变量
int a[5]; //定义数组类型变量
int *p[5]; //定义指针数组变量
int (*p)[5]; //定义数组指针变量
int (*fun_ptr)(int,int); //定义函数指针变量
int (*fun_ptr_arr[3])(int, int); //定义函数指针数组的指针变量
int **pptr; //定义二级指针变量
提取类型
//提取变量的类型就是把变量名去掉剩下的部分
int ; //定义普通数据类型
int *; //定义指针类型
int [5]; //定义数组类型
int *[5]; //定义指针数组类型
int (*)[5]; //定义数组指针类型
int (*)(int,int); //定义函数指针类型
int (*[3])(int, int); //定义函数指针数组的指针类型
int **; //定义二级指针类型
给类型重新起名
typedef int A; //重定义int类型为A类型
typedef int *Ptr; //重定义int *类型为Ptr
typedef int Arr[5]; //重定义int [5]类型为Arr
typedef int *Ptr_Arr[5]; //重定义int * [5]类型为Ptr_Arr
typedef int (*Arr_Ptr)[5]; //重定义int (*)[5]类型为Arr_Ptr
typedef int (*Fun_Ptr)(int,int); //重定义int (*)(int)类型为Fun_ptr
typedef int (*Fun_Ptr_Arr[3])(int, int); //重定义int (* [5])(int)类型为Fun_Ptr_Arr
typedef int **PPtr; //重定义int **类型为PPtr
给类型起多个名
typedef int *Ptr_i, int32;
//int32是一个int类型的重命名
//Ptr_i是一个int *类型的重命名
类型重定义与宏定义的区别
宏定义只是单纯的替换,不做任何正确性检测,是一个预处理指令
类型重定义,需要做正确性检测,是一条语句
宏替换发生在预处理阶段,而类型重定义发生在编译阶段
#define ptr_i int* //会将uint32替换成int
typedef int * pptr_i; //类型重定义
int main(int argc, const char *argv[])
{
ptr_i a,b; //a是指针类型,b是普通int类型 int *a, b;
pptr_i m,n; //m和n都是指针类型
return 0;
}
五、结构体
1、定义
有相同数据类型和不同数据类型构成的集合叫结构体,属于构造数据类型
struct 结构体名
{
//属性列表
/*成员类型1 成员变量1;
成员类型2 成员变量2;
。。。
成员类型n 成员变量n;*/
};
//声明一个英雄结构体类型
struct Hero
{
char name[20]; //姓名
int Hp; //血量
double speed; //基础位移
int kill; //人头数
};//(注意要加分号)
2、结构体变量的定义及初始化
#include
//声明一个英雄结构体类型
struct Hero
{
char name[20]; //姓名
int Hp; //血量
double speed; //基础位移
int kill; //人头数
}h3 = {"盖伦", 3500, 500, 5};
//练习:定义一个商品类型,成员属性:商品名称(name)、产地(position)、单价(price)、重量(weight)
struct //无名结构体
{
char name[40]; //名称
char position[40]; //产地
double price; //单价
double weight; //重量
}g1 = {"三鹿奶粉", "China", 350, 1000};
int main(int argc, const char *argv[])
{
//使用英雄类型定义一个英雄变量
struct Hero h1 = {"亚索", 650, 350, 0}; //此时定一个英雄变量h1
//定义英雄变量,指定某个成员进行赋值
struct Hero h2 = {.Hp=2000, .speed = 1000};
return 0;
}
3、结构体变量访问成员
(1)成员运算符 ".",个人读作"的";例如h1.name 表示h1结构体变量的name成员
(2)不要企图通过结构体变量名直接输出所有成员,如果想要输出结构体变量中的成员,需要使用成员运算符一个一个找
(3)相同类型的结构体变量之间是可以之间互相赋值的
(4)也可以对结构体变量取地址运算,其地址跟第一个成员属性的地址保持一致
//声明一个英雄结构体类型
struct Hero
{
char name[20]; //姓名
int Hp; //血量
double speed; //基础位移
int kill; //人头数
};//h3 = {"盖伦", 3500, 500, 5};定义一个结构体并初始化
int main()
{
//定义一个英雄变量h1并赋值
struct hero h1={"亚索",650,350,0};
//输出英雄变量h1中的内容
printf("h1.name = %s\n", h1.name);
printf("h1.Hp = %d\n", h1.Hp);
printf("h1.speed = %lf\n", h1.speed);
printf("h1.kill = %d\n", h1.kill);
}
4、结构体指针访问成员
结构体指针访问成员使用运算符"->"
使用格式:指针名->属性名
//声明一个英雄结构体类型
struct Hero
{
char name[20]; //姓名
int Hp; //血量
double speed; //基础位移
int kill; //人头数
};
int main()
{
//在堆区申请一个英雄类型,完成初始化并输出相应的属性
struct Hero *ptr = (struct Hero*)malloc(sizeof(struct Hero));
//给英雄名字赋值
strcpy(ptr->name,"亚瑟"); //给姓名赋值(因为是字符串赋值用到strcpy函数)
ptr->Hp = 3000; //给hp属性赋值
ptr->speed = 350;
ptr->kill = 3;
//输出英雄指针指向堆区空间中的内容
printf("英雄信息为:%s %d %.2lf %d\n", ptr->name, ptr->Hp, ptr->speed, ptr->kill);
}
2、整理思维导图