文章目录
一、指针
1 指针定义
首先理解“地址”的定义,地址 就是内存中每个字节对应的编号。此处的地址并没有存储起来。
程序运行时就会给变量分配内存单元。
直接访问: 按照变量地址存取变量值。比如scanf(“%d”,&c);
间接访问: 把变量i的地址存放到另一个变量中。获取数据时先获取变量的地址,再通过地址去找到变量的数据。
可以简单理解为 直接访问 是通过变量名去获取变量的数据,间接访问是先获取到一个地址,再根据地址去获取数据
区分指针和指针变量
指针:实际上就是变量的地址。可以把变量的地址称作变量的指针
指针变量:是一个变量,存放的是另外变量的指针(地址)。上图右边 i_pointer 就是一个指针变量
#include<stdio.h>
int main() {
int i = 5;
//定义一个指针变量,i_pointer才是指针变量名
int* i_pointer;
//如果程序是64位,指针变量的大小为8,如果是32位,指针变量大小为4
int size = sizeof(i_pointer);
printf("%d %d\n", i,size);//直接访问
return 0;
}
2 取地址符、取值操作符
&:是取地址符,也可以叫做 引用。通过它可以获得变量的地址。
*:是取值操作符,也叫 解引用。可以得到一个地址所对应的数据。
#include<stdio.h>
int main() {
int i = 5;
//定义一个指针变量,i_pointer才是指针变量名
//指针变量的初始化:某个变量取地址,不能随便写,如下面
int* i_pointer = &i;
//如果程序是64位,指针变量的大小为8,如果是32位,指针变量大小为4
int size = sizeof(i_pointer);
printf("%d %d\n", i,size);//直接访问
printf("*i_pointer = %d\n", *i_pointer);//间接访问,输出的是数值
printf("i_pointer = %d\n", i_pointer);//输出的是地址
return 0;
}
输出:
5 4
*i_pointer = 5
i_pointer = 13630628
注意:
3 指针的适用场景
3.1 指针传递
#include<stdio.h>
//指针变量,通过解析地址修改该地址所存的数据
void change(int *j) {
*j = 5;
}
int main() {
//2.指针传递
int i = 10;
printf("before_i = %d\n", i);
//C语言函数调用是值传递,值传递就是 实参赋值给形参
//i的值不改变 是因为i和j的地址不一样,不在同一块区域,所以改变的不是同一个值
//change(i);
change(&i);//取地址,引用,传入地址
printf("after_i = %d\n", i);
return 0;
}
值传递和引用传递的地址变化:
值传递
引用传递的实际效果是 j=&i ,实际上还是值传递(可以看到实际上j是指向了i的地址,相当于j间接和i指向同一地址)
程序的执行过程实际上就是内存的变化过程,我们要关注栈空间的变化。
3.2 指针偏移
指针的偏移就是地址的加减变化,指针的乘除运算并没有意义
//3.指针偏移,地址的加减
int a[5] = { 1,2,4,7,3 };//数组名存了数组的起始地址
int* p;
p = a;//z地址
for (int i = 0; i < 5; i++) {
//printf("%d ", *p + i);//这样写是不对的,这样是先获取了p所指地址的数据以后,对数据进行加减
printf("%d ", *(p + i));//必须要用括号括起来,这样才是地址偏移,再去查找对应的数据
}
printf("\n");
//指向数组最后一个
p = &a[4];
for (int i = 0; i < 5; i++) {
//printf("%d ", *p + i);//这样写是不对的,这样是先获取了p所指地址的数据以后,对数据进行加减
printf("%d ", *(p - i));//必须要用括号括起来,这样才是地址偏移,再去查找对应的数据
}
printf("\n");
指针加 1 以后偏移的长度是其基类型的长度,实际上是偏移 sizeof(int) ,sizeof(float)
3.3.1 指针与一维数组(偏移)
#include<stdio.h>
void change_pointer(char* j) {//指针变量,通过解析地址修改该地址所存的数据
*j = 'H';
j[1] = 'E';
*(j + 2) = 'L';
}
int main() {
//4.指针和一维数组
char c[10] = "hello";
//数组名作为实参传入函数时,是弱化为指针
change_pointer(c);
puts(c);
return 0;
}
4 动态内存申请
栈空间的大小是编译时确定的。使用空间大小不确定的话就要使用堆空间
堆空间需要用到malloc函数,执行malloc函数时,需要传入一个整型变量
即:
① *p = malloc(size),此处size是一个整型变量;②malloc函数的返回值是 void类型;
③void类型(无类型)的指针并不能进行偏移,它只能存储一个地址;无类型的原因是系统并不知道我们申请的空间是用来存放什么类型的数据;
④所以需要对malloc返回的指针进行强制转换;eg: ** p = (char) malloc(size)**
eg:这张图展示了堆空间和栈空间,程序从main函数开始执行,于是给main函数分配了一部分栈空间,其中有在main函数中定义的 i 和 p。
而通过malloc申请的空间会返回一个堆空间的首地址,可以把首地址存在 指针变量 p 中,然后可以利用str函数往对应的空间存储字符数据
堆空间和栈空间对比:
栈是系统提供的;
堆是函数提供的,需要自己申请释放
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//返回值是指针
char* print_stack() {//栈空间
char c[100] = "print_stack func\n";
puts(c);
return c;
}
//堆空间
char* print_malloc() {
int size = 100;
char* p = (char*)malloc(size);
char* q;
q = p;
strcpy(q, "print_malloc func");
puts(q);
return q;
}
int main() {
char* p;
char* m;
//栈空间
p = print_stack();
//puts(p);//这里会出现乱码
//出现乱码的原因:当print_stack这个函数执行完以后,栈空间将其释放掉,给下一个函数使用,所以会出现乱码
//堆空间
m = print_malloc();
//此时可以正常输出,因为堆空间在整个进程当中不会因为函数结束而释放,只有free的时候才会释放
puts(m);
free(m);//释放
puts(m);//输出乱码
return 0;
}
输出结果:
// 栈空间输出
print_stack func
// 系统自动释放这个函数的空间
x齭
// 堆空间输出
print_malloc func
print_malloc func
// 手动释放以后
葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺
二、结构体
1 结构体初始化
结构体可以整合不同数据类型,关键字 struct ; 在主函数外定义
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//**①结构体定义**
struct student {
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
};//这里要有分号
int main() {
//**②结构体声明**
struct student s = { 1001,"lele",'M',20,85.4,"Shenzhen" };
struct student sarr[3];
int i;
s.num = 1003;
//结构体输出需要单个访问内部的成员 **结构体变量.成员名**
printf("%d %s %c %d %f %s\n", s.num, s.name, s.sex, s.age, s.score, s.addr);
printf("--------------------------------------\n");
// scanf("%d%s %c%d%f%s",&s.num,s.name,&s.sex,&s.age,&s.score,s.addr);
for (i = 0; i < 3; i++)
{
//输入函数后面一定不可以加'\n'!!!!!!!!
// %c 会读取空格
scanf("%d %s %c %d %f %s", &sarr[i].num, sarr[i].name, &sarr[i].sex, &sarr[i].age, &sarr[i].score, sarr[i].addr);
}
for (i = 0; i < 3; i++)
{
printf("%d %s %c %d %f %s\n", sarr[i].num, sarr[i].name, sarr[i].sex, sarr[i].age, sarr[i].score, sarr[i].addr);
}
return 0;
}
2 结构体对齐
就是结构体大小一定是 结构体中最大成员的整数倍,这里的最大成员是说哪个类型的的字节数最多,sizeof(int) = 4,sizeof(char) = 1
比如最大成员是int,int是4字节,结构体的大小就要是4的整数倍
为什么要对齐:为了cpu高效运行,cpu拿数据时按照4的整数倍取
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//如果两个小存储小于最大长度的话,两个小的会拼在一起
struct student_type1 {
double score;//8 float 4
short age;//2 整型
};
struct student_type2 {
double score;//8
// 相当于 8+(4+2),把这个(4+2)凑成8
int height;//4
short age;//2
};
struct student_type3 {
int height;//4
//char short两个一起共同占用4个字节,节省结构体空间
char sex;//1
short age;//2
};
int main() {
struct student_type1 s1;
struct student_type2 s2 = { 1,2,3 };
struct student_type3 s3;
printf("s1 size=%d\n", sizeof(s1));//16 //8
printf("s2 size=%d\n", sizeof(s2));//16
printf("s3 size=%d\n", sizeof(s3));//8 //4的整数倍
return 0;
}
3 结构体指针
设置指针变量使其指向一个结构体变量。
//结构体指针
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
struct student {
int num;
char name[20];
char sex;
};//这里要有分号
int main() {
struct student s;
struct student arr[3] = { 1001,"lili",'M',1002,"lele",'M',103,"xixi",'F' };//结构体数组
//结构体指针,获取成员时要使用 ->
struct student* p;
int i;
//取地址
p = &s;
//①方式一获取成员
scanf("%d %s %c", &p->num, p->name, &p->sex);
//②方式二获取优先级
// 因为 . 这个成员选择运算符的优先级高于 *,所以如果用指针指向成员且要用 . 的话需要加括号
//scanf("%d %s %c", &(*p).num, (*p).name, &(*p).sex);
printf("%d %s %c\n", p->num, p->name, p->sex);
//指向数组
p = arr;
printf("%d %s %c\n", p->num, p->name, p->sex);//输出arr[0]
p = p + 1;
printf("%d %s %c\n", p->num, p->name, p->sex);//输出arr[1]
p = p + 1;
printf("%d %s %c\n", p->num, p->name, p->sex);//输出arr[2]
return 0;
}
4 typedef
typedef实际就是取别名,可以使程序更简洁
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
// 使用typedef的话就不用在主函数里再写 struct student s
//可以写 stu s
// stu等价于 struct student
// p_stu 等价于 struct student*
typedef struct student {
int num;
char name[20];
char sex;
}stu,*p_stu;// ①typedef给结构体定义别名
//给int取别名
typedef int INGETER; //修改时比较方便,比如直接修改成short
int main() {
stu s;
stu arr[3] = { 1001,"lili",'M',1002,"lele",'M',103,"xixi",'F' };//结构体数组
p_stu p;
//局部变量可以和某一个结构内的成员名字相同
//int num;
INGETER num;
//取地址
p = &s;
scanf("%d %s %c", &p->num, p->name, &p->sex);
printf("%d %s %c\n", p->num, p->name, p->sex);
//指向数组
p = arr;
printf("%d %s %c\n", p->num, p->name, p->sex);//输出arr[0]
p = p + 1;
printf("%d %s %c\n", p->num, p->name, p->sex);//输出arr[1]
p = p + 1;
printf("%d %s %c\n", p->num, p->name, p->sex);//输出arr[2]
return 0;
}