目录
1、指针的本质
1.1 指针的定义
一般在内存地址中存储的是数据,如果我们需要将某个变量的地址保存下来,就需要用到指针。
1.2 取地址操作符与取值操作符
取地址操作符为&,也称引用,通过该操作符我们可以获取一个变量的地址值;取值操作符为*,也称为解引用,通过该操作符我们可以得到一个地址对应的数据。
注意
(1)指针的变量名是p,而不是*p
(2)在定义指针变量时必须指定其类型。需要注意的是,只有整型变量的地址才能放到指向整型变量的指针变量中,依此类推。
(3)连续定义三个指针变量的正确写法
int* a, * b, * c;//定义三个整型的指针变量
int* a, b, c;//一个整型指针变量,两个整型变量
2、指针的使用场景
2.1 指针的传递
普通值传递
#include <stdio.h>
void change(int j);
int main()
{
int i = 10;
printf("before change i=%d\n", i);
change(i);
printf("after change i=%d\n", i);
return 0;
}
void change(int j)
{
j = 5;
}
经过change函数,i的值仍然为10
指针传递
#include <stdio.h>
void change(int* j);
int main()
{
int i = 10;
printf("before change i=%d\n", i);
change(&i);
printf("after change i=%d\n", i);
return 0;
}
void change(int* j)//j=&i
{
*j = 5;
}
经过change函数,i的值改变
2.2 指针的偏移
指针即地址,就像我们找到了一栋楼,这栋楼的楼号是B,那么住前就是A,往后就是C,所以应用指针的另一个场景就是对其进行加减,但对指针进行乘除是没有意义的,就像家庭住址乘以5没有意义一样。
数组是特殊的,不能和整型变量、浮点型、字符型变量类比。
数组名a,类型是数组,a里边存了一个值,是地址值,是数组的起始地址
#include <stdio.h>
int main()
{
int a[5] = { 1,2,3,4,5 };
int* p;
p = a;
}
#include <stdio.h>
int main()
{
int a[5] = { 1,2,3,4,5 };
int* p;
p = a;
printf("*p=%d\n", *p);
for (int i = 0; i < 5; i++)
{
printf("%d\n", *(p + i));
}
return 0;
}
2.3 指针与自增、自减运算符
#include <stdio.h>
int main()
{
int a[3] = { 2,7,8 };
int* p;
int j;
p = a;//让指针变量p,指向数组的开头
j = *p++;//j=*p;p++,任何时候都是把后加加去掉,第二步,另一个运算符看优先级是否高于++
printf("a[0]=%d,j=%d,*p=%d\n", a[0], j, *p);
return 0;
}
#include <stdio.h>
int main()
{
int a[3] = { 2,7,8 };
int* p;
int j;
p = a;//让指针变量p,指向数组的开头
j = (*p)++;//j=*p;p++,任何时候都是把后加加去掉,第二步,另一个运算符看优先级是否高于++
printf("a[0]=%d,j=%d,*p=%d\n", a[0], j, *p);
return 0;
}
#include <stdio.h>
int main()
{
int a[3] = { 2,7,8 };
int* p;
int j;
p = a;//让指针变量p,指向数组的开头
j = *p++;//j=*p;p++,任何时候都是把后加加去掉,第二步,另一个运算符看优先级是否高于++
printf("a[0]=%d,j=%d,*p=%d\n", a[0], j, *p);//227
j = p[0]++;//j=p[0],p[0]++
printf("a[0]=%d,j=%d,*p=%d\n", a[0], j, *p);//2,7,8
return 0;
}
2.4 指针与一维数组
#include <stdio.h>
void change(char* d);
int main()
{
char c[10] = "hello";
change(c);
puts(c);
return 0;
}
void change(char* d)
{
*d = 'H';
}
#include <stdio.h>
void change(char* d);
int main()
{
char c[10] = "hello";
change(c);
puts(c);
return 0;
}
void change(char* d)
{
*d = 'H';
d[1] = 'E';
*(d + 2) = 'L';
}
2.5 指针与动态内存申请
在学习完数组之后会觉得数组长度固定很不方便,其实C语言的数组长度固定是因为其定义的整型、浮点型、字符型变量、数组变量都在栈空间中,而栈空间的大小在编译时是确定的。如果使用的空间大小不确定,那么就要使用堆空间。(最新的C11数组变量可变,但是因为其放在堆空间了)
栈是计算机系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈操作、出栈操作都有专门的指令执行,这就决定了栈的效率比较高,堆则是C/C++函数库提供的数据结构,它的机制很复杂,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能由于内存碎片太多),那么就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后返回。栈空间由系统自动管理,而堆空间的申请和释放需要自行管理。
程序是放在磁盘上的有序的指令集合,程序启动起来时才叫进程。
malloc函数可以实现动态申请空间
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int place_apply;//申请多大的空间
scanf("%d", &place_apply);
char* p;
p = (char*)malloc(place_apply);//malloc申请空间的单位是字节
//malloc返回的是void*无类型指针,需要强转成char*类型,使与p的类型一致
strcpy(p, "malloc success");
puts(p);
return 0;
}
申请多大空间用完之后要使用free函数用于释放申请的空间,free之后要将p赋值为NULL,防止野指针
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int place_apply;//申请多大的空间
scanf("%d", &place_apply);
char* p;
p = (char*)malloc(place_apply);//malloc申请空间的单位是字节
//malloc返回的是void*无类型指针,需要强转成char*类型,使与p的类型一致
strcpy(p, "malloc success");
puts(p);
free(p);//释放空间,free时必须使用malloc申请时返回的指针值,不能进行任何偏移
p = NULL;
return 0;
}
栈空间与堆空间的区别
#include <stdio.h>
char* print_stack();
int main()
{
char* p;
p = print_stack();
puts(p);//不能打印,栈空间会随着函数的执行结束而释放
return 0;
}
char* print_stack()
{
char c[17] = "I am print_stack";
puts(c);//能正常打印
return c;
}
#include <stdio.h>
char* print_stack();
char* print_malloc();
int main()
{
char* p;
p = print_stack();
//puts(p);//不能打印,栈空间会随着函数的执行结束而释放
p = print_malloc();
puts(p);//可以正常打印,堆空间不会随子函数的结束而释放,必须自己free
return 0;
}
char* print_malloc()
{
char* p = (char*)malloc(30);
strcpy(p, "I am print_malloc");
puts(p);//可以正常打印
return p;
}
char* print_stack()
{
char c[17] = "I am print_stack";
puts(c);//能正常打印
return c;
}
2.6 字符指针与字符数组的初始化
#include <stdio.h>
int main()
{
char* p = "hello";//把字符串型常量"hello"的首地址赋给p
char c[10] = "hello";//等价于strcpy(c,"hello");
c[0] = 'H';
p[0] = 'H';//不可以对常量区数据进行修改
printf("c[0]=%c\n", c[0]);
printf("p[0]=%c\n", p[0]);
p = "world";//将字符串world的地址赋给p
c = "world";//非法
return 0;
}
3、二级指针
一级指针的使用场景是传递和偏移,服务的对象是整型变量、浮点型变量、字符型变量等。二级指针也是一种指针,其作用自然也是传递与偏移,其服务对象更加简单,即只服务于一级指针的传递与偏移。
3.1 二级指针的传递
如果p是一个指针变量,那么要存储p的地址的话,&p就是一个二级指针,要存储二级指针就要定义一个二级指针变量,所以二级指针的初始化一定是某一个一级指针取地址
#include <stdio.h>
void change(int** p, int* pj);
int main()
{
int i = 10;
int j = 5;
int* pi;
int* pj;
pi = &i;
pj = &j;
printf("i=%d,*pi=%d,*pj=%d\n", i, *pi, *pj);//i和*pi等于10,*pj=5
change(&pi, pj);
printf("after change i=%d,*pi=%d,*pj=%d\n", i, *pi, *pj);//目标是让*pi的值为5
return 0;
}
void change(int** p, int* pj)
{
int i = 5;
*p = pj;
}