左值:既能放到等号左侧、也能放到等号右侧的值称为左值,有地址空间,例如:变量、堆区申请的空间、地址返回的函数返回值
右值:只能放到等号右侧的值,没有地址空间,例如:常量、临时值、表达式的结果、值返回的函数返回值
普通变量作为函数参数传递是单向的值传递,只是将实参的值复制一份给形参变量,形参的改变不会影响实参的值,因为所在内存空间不同
如果传递的是地址,被调函数使用指针接收,如果在被调函数中,没有更改指针指向空间中的内容,只改变指向,依然是值传递
指针、数组名作为函数参数传递,是地址传递,需要在被调函数中更改指针指向空间中的内容,形参内容的改变,实参也跟着改变
普通变量通过函数返回值进行返回是单向的值返回,在主调函数中,该函数的返回值只能作为右值使用,不能被重新赋值
需要返回生命周期比较长的变量地址(全局变量、静态局部变量、堆区申请空间、主调函数地址传递的空间),该函数的返回值是一个左值,可以直接使用,也可以被重新赋值,被重新赋值后,被调函数中该空间中的内容也跟着改变
例:
#include
/*************************值传递************************/
void swap(int m, int n) //传递num和key
{
int temp;
temp = m;
m = n;
n = temp; //三杯水交换
printf("m = %d, n = %d\n", m, n); //1314 520
}
/************************值传递*****************************/
void fun(int *p, int *q) //传递num和key的地址
{
int *temp;
temp = p;
p = q;
q = temp; //三杯水交换,这里交换的是p和q的指向,并没有改变*p和*q的内容
printf("*p = %d, *q = %d\n", *p, *q); //1314 520
}
/*********************地址传递********************************/
void gun(int *p, int *q) //传递num和key的地址
{
int temp;
temp = *p;
*p = *q;
*q = temp; //三杯水交换,交换*p和*q的内容
printf("*p = %d, *q = %d\n", *p, *q); //1314 520
}
/***********************值返回********************************/
int hun() //int k = 100;
{
int value = 666;
return value; //返回value
}
/*************************地址返回*****************************/
int *iun()
{
static int value = 999; //静态变量虽然在函数体内定义,但是不占函数的内存空间
return &value; //返回静态局部变量的地址
}
/*************************主函数***************************/
int main(int argc, const char *argv[])
{
int num = 520;
int key = 1314;
//调用swap函数交换两数
swap(num, key);
printf("调用swap后,主函数中num = %d, key = %d\n", num, key);
//调用fun函数交换两数
fun(&num, &key);
printf("调用fun后,主函数中num = %d, key = %d\n", num, key);
//调用gun函数交换两数
gun(&num, &key);
printf("调用gun后,主函数中num = %d, key = %d\n", num, key);
//调用hun函数
//hun() = 999;
int ret = hun(); //值返回的函数返回值只能是右值
printf("hun() = %d\n", hun()); //666
//调用iun函数
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;
}
C语言中可以使用malloc和free来对堆区空间进行操作
malloc、free函数原型:
#include //函数头文件
void *malloc(size_t size);
功能:允许程序员手动从堆区空间申请内存
参数:要申请的空间大小,以字节为单位,一般格式为 sizeof(类型名)*n
返回值:是一个万能指针,可以使用强制类型转化为自己想要的类型的指针,如果申请成功,则将堆区申请的空间地址返回,如果申请失败,返回NULL
void free(void *ptr);
功能:释放程序员从堆区申请的空间
参数:要释放的空间首地址
返回值:无
单个空间内存的申请:数据类型 *指针名 = (数据类型*)malloc(sizeof(数据类型)));
连续内存的申请: 数据类型 *指针名 = (数据类型*)malloc(sizeof(数据类型)*n));
释放:free(指针名);
#include
#include //malloc所在的头文件
int main(int argc, const char *argv[])
{
//在堆区申请一个int类型的空间大小
int *p1 = (int *)malloc(4); //申请4字节的大小
printf("*p1 = %d\n", *p1); //随机值
int *p2 = (int *)malloc(sizeof(int)); //申请一个int类型的大小
*p2 = 520; //给堆区空间进行赋值
printf("*p2 = %d\n", *p2); //520
//连续申请5个int类型的大小
int *p3 = (int *)malloc(sizeof(int)*5);
//输出默认值
for(int i=0; i<5; i++)
{
//printf("%d\t", p3[i]);
printf("%d\t", *(p3+i));
}
printf("\n");
//释放堆区空间
free(p1);
p1 = NULL; //防止野指针
free(p2);
p2 = NULL;
free(p3);
p3 = NULL;
return 0;
}
类型重定义本质上是给类型重新起个名字,使得代码更加易于理解,例如:将unsigned long int 改名为uint64,将unsigned short int 改名为uint16
typedef 数据类型名 新名;
例:typedef unsigned long int uint64;
typedef unsigned short int uint16;
int a; //定义普通变量
int *ptr; //定义指针类型变量
int arr[5]; //定义数组类型变量
int *ptr_arr[5]; //定义指针数组变量
int (*arr_ptr)[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; //重定义指针类型
typedef int ARR[5]; //重定义数组类型
typedef int *Ptr_Arr[5]; //重定义指针数组
typedef int (*Arr_Ptr)[5]; //重定义数组指针
typedef int (*Fun_Ptr)(int,int); //重定义函数指针
typedef int (*Fun_Ptr_Arr[3])(int, int); //重定义函数指针数组的指针
typedef int **PPtr; //重定义二级指针
例:
#include
#include
typedef unsigned short int uint16; //将无符号短整形重命名为uint16
typedef int * Ptr_i; //将int*类型重命名为Ptr_i
typedef char String[10]; //将char [5]类型重命名为String
int main(int argc, const char *argv[])
{
uint16 num = 520; //等价于unsigned short int num = 520
printf("sizeof num = %ld\n", sizeof(num)); //2
printf("num = %d\n", num); //520
Ptr_i p1 = NULL; //此时p1是指针变量 int *p1;
printf("sizeof p1 = %ld\n", sizeof(p1)); //8
String s1; //此时是定义的长度为10的字符数组
strcpy(s1, "hello");
printf("s1 = %s\n", s1); //hello
printf("sizeof s1 = %ld\n", sizeof(s1)); //10
printf("strlen s1 = %ld\n", strlen(s1)); //5
return 0;
}
#include
typedef int *Ptr_i, int32; //int32是一个int类型的重命名
//Ptr_i是int*类型的重命名
int main(int argc, const char *argv[])
{
int32 num; //num是一个普通变量
Ptr_i p; //p是一个int×类型的变量
printf("sizeof num = %ld\n", sizeof(num)); //4
printf("sizeof p = %ld\n", sizeof(p)); //8
return 0;
}
#include
#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都是指针类型
printf("sizeof a = %ld\n", sizeof(a)); //8
printf("sizeof b = %ld\n", sizeof(b)); //4
printf("sizeof m = %ld\n", sizeof(m)); //8
printf("sizeof n = %ld\n", sizeof(n)); //8
return 0;
}
系统提供的数据类型不够用了,没有条件创造条件,自己定义数据类型,自己用
有相同数据类型和不同数据类型构成的集合叫结构体,属于构造数据类型
struct 结构体名
{
//属性列表
成员类型1 成员变量1;
成员类型2 成员变量2;
。。。
成员类型n 成员变量n;
};
注意:
1、struct是定义结构体的关键字
2、结构体名:是一个标识符,要符合标识符的命名规则,一般首字母大写
3、所有的成员属性使用一对花括号包裹,最后用分号结束,分号不能省略
4、成员属性类型可以是基本数据类型,也可以是构造数据类型
5、声明结构体不占内存空间,使用结构体类型定义变量时,变量会占用内存空间
6、一般将声明结构体放在文件头部或者头文件中,也可以声明在其他部分,但是,至少要声明在使用之前
#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;
}
结构体指针访问成员使用运算符"->"
使用格式:指针名->属性名
#include
#include
#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};
//输出英雄变量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);
//在堆区申请一个英雄类型,完成初始化并输出相应的属性
struct Hero *ptr = (struct Hero*)malloc(sizeof(struct Hero));
//给英雄名字赋值
strcpy(ptr->name,"亚瑟"); //给姓名赋值
ptr->Hp = 3000; //给hp属性赋值
ptr->speed = 350;
ptr->kill = 3;
//输出英雄指针指向堆区空间中的内容
printf("英雄信息为:%s %d %.2lf %d\n", ptr->name, ptr->Hp, ptr->speed, ptr->kill);
printf("*********************************************************************\n");
//练习:使用商品结构体定义一个商品变量,不用初始化,使用scanf将每个属性进行输入
//输入完该变量后,再将该变量的所有信息输出
printf("请输入商品的名称:");
scanf("%s", g1.name);
printf("请输入商品的产地:");
scanf("%s", g1.position);
printf("请输入商品的单价:");
scanf("%lf", &g1.price);
printf("请输入商品的重量:");
scanf("%lf", &g1.weight);
//输出信息
printf("商品信息:%s %s %.2lf %.2lf\n", g1.name, g1.position, g1.price, g1.weight);
return 0;
}
结构体数组本质上是一个数组,数组的每个元素都是结构体类型
数组的操作跟基本数据类型一致
#include
//定义一个学生结构体
struct Stu
{
char name[20]; //姓名属性
int age; //年龄
double score; //分数
char sex; //性别
};
int main(int argc, const char *argv[])
{
//定义三个学生,对这三个学生进行管理
struct Stu s[3] =\
{{"张三",18,99,'M'},\
{"李四", 20, 80, 'W'},\
{"王五", 25, 100, 'M'}}; //定义了三个学生变量,s[0]、s[1]、s[2]
//输出三名学生的信息
printf("姓名\t年龄\t分数\t性别\n");
for(int i=0; i<3; i++)
{
printf("%s\t%d\t%.2lf\t%c\n", s[i].name, s[i].age, s[i].score, s[i].sex);
}
//通过键盘更新第二名学生的信息 s[1]
printf("请输入第二名学生的姓名:");
scanf("%s", s[1].name); // (s+1)->name
printf("请输入第二名学生的分数:");
scanf("%lf", &s[1].score);
printf("%s\t%d\t%.2lf\t%c\n", s[1].name, s[1].age, s[1].score, s[1].sex);
return 0;
}
练习:
声明一个结构体类型,英雄类型,成员属性:姓名(name)、人头数(kill)、死亡数(death)、助攻(asst),使用该英雄类型定义5个英雄,调用函数输入这5名英雄的数据,再调用函数实现对五名英雄按人头数降序排序,封装函数完成求出本场的MVP(计算方式:人头数-死亡数据+助攻数,最大值为MVP),最后输出排序后的所有英雄的信息,以及本场MVP的信息
#include
#include //malloc的头文件
#include
//定义英雄类型
struct Hero
{
char name[20]; //姓名
int kill; //人头
int death; //死亡数
int asst; //助攻数
};
/**************************定义申请内存空间函数**********************/
struct Hero *getHeros(int num)
{
//在堆区进行申请num个英雄
struct Hero * H = (struct Hero *)malloc(sizeof(struct Hero)*num);
if(NULL==H)
{
printf("空间申请失败\n");
return NULL;
}else
{
printf("最强团队已经产生\n");
return H;
}
}
/*****************************定义输入英雄数据函数******************/
void inputHeros(struct Hero *H, int num)
{
//判断传过来的指针是否合法
if(NULL == H)
{
printf("英雄团队不合法\n");
return;
}
//开始输入
for(int i=0; iname);
printf("请输入第%d个英雄斩获人头数:", i+1);
scanf("%d", &(H+i)->kill);
printf("请输入第%d个英雄的死亡数据:", i+1);
scanf("%d", &(H+i)->death);
printf("请输入第%d个英雄的助攻数据:", i+1);
scanf("%d", &(H+i)->asst);
printf("\n");
}
printf("本局比赛已经结束\n");
}
/********************对英雄数据按人头进行排序***************/
void sortHeros(struct Hero *H, int num)
{
//判断传过来的指针是否合法
if(NULL == H)
{
printf("英雄团队不合法\n");
return;
}
//开始排序
for(int i=1; i
#include
//定义一个生日类型
struct Birthday
{
int year;
int month;
int day;
};
//定义一个书本类型
struct Book
{
char name[20]; //书名
double price; //价格
};
//定义一个学生类型
struct Stu
{
char name[20]; //姓名
int score; //分数
//定义一个生日变量
struct Birthday bir; //生日
//定义一个书本类型的指针
struct Book *b;
};
int main(int argc, const char *argv[])
{
struct Stu s1;
strcpy(s1.name, "张三");
s1.score = 90;
//如果结构体变量中包含一个结构体变量,要找到最底层的数据
//需要使用成员运算符,一级一级找到最低一级数据进行访问
s1.bir.year = 2023;
s1.bir.month = 8;
s1.bir.day = 2;
printf("%s\t%d\t%4d\t%02d\t%02d\n", s1.name,s1.score, s1.bir.year, s1.bir.month, s1.bir.day);
/****************************定义结构体指针指向结构体变量*****************************/
struct Stu *p = &s1;
p->bir.month = 10;
printf("%s\t%d\t%4d\t%02d\t%02d\n", p->name,p->score, p->bir.year, p->bir.month, p->bir.day);
/*****************************结构体变量访问成员结构体指针中的数据*****************/
s1.b = (struct Book*)malloc(sizeof(struct Book));
//通过结构体变量访问成员变量结构体指针中的数据
strcpy(s1.b->name,"朝花夕拾");
//使用结构体指针访问成员指针的属性
p->b->price = 100;
return 0;
}
#include
//定义一个全局函数
void show(const char* s)
{
printf("%s\n", s);
}
//定义一个结构体,封装一个函数指针
struct Stu
{
char name[20];
int age;
void (*display)(const char *); //封装一个函数指针
};
int main(int argc, const char *argv[])
{
struct Stu s;
s.display = show; //给成员指针赋值
s.display("hello world"); //调用成员函数
return 0;
}
可以给结构体类型重新命名,也可以给结构体指针重新命名
#include
//方式1:定义结构体时,顺便重命名
typedef struct Stu
{
char name[20]; //姓名
int age; //年龄
double score; //分数
}stu_struct, *stu_ptr; //给结构体名两个名
typedef struct Stu Stu; //方式2:给结构体类型重命名
//方式3:给无名结构体重命名
typedef struct
{
char name[20]; //狗名
char class[20]; //品种
}Dog;
int main(int argc, const char *argv[])
{
stu_struct s1; //结构体变量
stu_ptr p1; //结构体指针
printf("sizeof s1 = %ld\n", sizeof(s1)); //?
printf("sizeof p1 = %ld\n", sizeof(p1)); //8
return 0;
}
一般而言,结构体的大小是各个成员变量所在内存之和
特殊而言,结构体需要进行字节对齐,有利于数据偏移,和寻址功能
在C语言中,一个空的结构体,所占内存大小为0字节
字节对齐:
#include
struct A
{
};
struct B // 1
{
char ch;
};
struct C // 1022
{
char ch;
short num;
};
struct D // 1120
{
short num;
char ch;
};
struct E // 11203333
{
short num;
char ch;
int key;
};
struct F // 100022223300
{
char ch;
int key;
short num;
};
struct G // 1000222233333333
{
char ch;
int key;
short* num;
};
struct H // 100000002222222233330000
{
char ch;
short* num;
int key;
};
struct I // 1000222233004400
{
struct F ch;
short num;
};
struct J // 1100100022223300
{
short num;
struct F ch;
};
struct K //
{
char arr[5];
int num;
};
int main(int argc, const char *argv[])
{
printf("sizeof(A) = %ld\n", sizeof(struct A)); //?
printf("sizeof(B) = %ld\n", sizeof(struct B)); //1
printf("sizeof(C) = %ld\n", sizeof(struct C)); //4
#if 0
struct C temp;
printf("&temp.ch = %p\n", &temp.ch);
printf("&temp.num = %p\n", &temp.num);
#endif
printf("sizeof(D) = %ld\n", sizeof(struct D)); //4
struct D temp;
printf("&temp.ch = %p\n", &temp.ch);
printf("&temp.num = %p\n", &temp.num);
printf("sizeof(E) = %ld\n", sizeof(struct E)); //8
printf("sizeof(F) = %ld\n", sizeof(struct F)); //12
printf("sizeof(G) = %ld\n", sizeof(struct G)); //16
printf("sizeof(H) = %ld\n", sizeof(struct H)); //24
printf("sizeof(I) = %ld\n", sizeof(struct I)); //16
printf("sizeof(J) = %ld\n", sizeof(struct J)); //16
printf("sizeof(K) = %ld\n", sizeof(struct K)); //12
return 0;
}