1. 指针本质
通俗的讲,指针就是地址,我们通常所说的指针就是指针变量,也就是保存地址的变量
- 定义方式 例如:int *a;//a就是指针变量
- “&”和“*” 两个运算符的优先级相同,都是按照自右向左的方向结合,因此 *&a与a等价,不将取值和取地址放在一起使用
#include <stdio.h>
int main(){
int i=5;//定义整型变量
char c='a';//定义字符变量
int *i_pointer=&i;//定义整型变量指针,并把i的地址赋值给i_pointer
char *c_pointer=&c;//定义整型变量指针,并把i的地址赋值给i_pointer
return 0;
}
指针分不同类型是因为取值可以拿到的空间大小不同,*i_pointer
可以拿到 4个字节空间,*c_pointer
可以拿到一个字节空间
通过间接访问可以修改变量的值, 其中指针的本质就是间接访问
- 指针变量本身的大小是固定的,对于64位系统,
sizeof (i_pointer)
和sizeof (c_pointer)
是8个字节;对于32位系统,它们都是4个字节 sizeof (*i_pointer)
等价为sizeof(i)
,是4个字节;sizeof (*c_pointer)
等价于sizeof(c)
,是1个字节
2. 指针的传递
- 函数调用时值传递
为什么i的值没有改变
我们再启动每个程序的时候,程序都会变成一个进程,每一个进程都有自己的进程空间,我们执行的程序被编译后是放在代码段的,pc指针就会一句一句执行我们写的代码交给cpu里面的译码器, main函数的栈空间就会给i分配4个字节空间,里面存的是10,调用change函数的时候,系统会为change函数分配栈空间, 每一个函数的栈空间是独立的,每调用一个函数,函数有自己的栈空间,当调用change函数的一瞬间,i的值赋给j,j=10,然后执行change函数,把j=10改成j=5了,main函数栈空间的i不会改变。
解决方法:
#include <stdio.h>
//指针的传递
void change(int *j)//等价于j=&i
{
*j=5;//间接访问得到变量i
}
int main(){
int i=10;
printf("before i= %d\n",i);
change(&i);//传递变量i的地址
printf("after i= %d\n",i);
return 0;
}
实际效果是j=&i,依然是值传递,只是这时j是一个指针变量,存储的是变量i的地址,所以通过*j
就间接访问到了与变量i相3同的区域,通过*j=5
就实现了对变量i的值的改变
3. 指针的偏移
指针即地址,就像我们找到了一栋楼,这栋楼的楼号是B,那么往前就是A,往后就是C,所以应用指针的另一个场景就是对其进行加减,把对指针的加减称为指针的偏移,加就是向后偏移,减就是向前偏移。
#include <stdio.h>
# define N 5
//指针的偏移,对指针进行加加减减
int main(){
int a[N]={1,2,3,4,5};//数组名内存储了数组的起始地址,
int *p;//定义指针变量p
p=a;//不需要取地址,因为a中存的就是一个地址值
int i;
for(i=0;i<N;i++){
printf("%3d",*(p+i));//这里于写a[i]等价
}
printf("\n------------------\n");
p=&a[4];//指针变量p指向数组的最后一个元素,需要取地址,因为a[4]表明的是一个具体的元素,通过&取地址
for (int i = 0; i < N; i++)
{
printf("%3d",*(p-i));
}
return 0;
}
- 指针变量加1后,偏移的长度是其基类型的长度,如果是int类型就是偏移 sizeof(int),这样通过
*(p+1)
就可以得到元素 a[1]
指针与一维数组
- 数组名作为实参传递给子函数,是弱化为指针的
#include <stdio.h>
//指针与一维数组的传递
//数组名作为实参传递给子函数,是弱化为指针的
//练习传递与偏移
void change(char *d)//char *d 与char d[]完全等价
{
*d='H';
*(d+1)='E';//等价与d[1]
}
int main(){
char c[10]="hello";
change(c);
puts(c);
return 0;
}
4. 指针与动态内存申请
C语言的数组长度固定是因为其定义的整型、浮点型、字符型变量、数组变量都在栈空间中,而栈空间的大小在编译时是确定的。 如果数组使用的空间大小不确定,那么就要使用堆空间
- 使用malloc动态申请堆空间,必须加上头文件
#include <stdlib.h>
- malloc函数的返回值类型是
void*
(无类型指针),因为无类型的指针时不能偏移的,使用时需要强制类型转换 - 在使用malloc时,一般参数传递的形式为(sizeof(要开辟的变量名)*要开辟的个数).例:
p=(int *)malloc(sizeof(int)*initsize)
- 使用完之后需要释放申请的空间,释放申请的空间给的地址,必须是最初malloc返回给我们的地址
- 指针的大小与其指向空间的大小是两码事,指针的大小永远是8个字节(64位系统)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
int size;//size代表我们要申请的字节空间
char *p;//void*类型指针不能偏移的
scanf("%d",&size);
p=(char *)malloc(size);
// p[0]='H';
// p[1]='O';
// p[2]='W';
// p[3]='\0';
strcpy(p,"malloc success");
puts(p);
free(p);//释放申请空间
return 0;
}
4. 栈空间和堆空间差异
栈在计算机执行任何程序都要转成汇编变成机器码执行,分配有专门的寄存器存放栈的地址,压栈操作、出栈操作都有专门的指令,所以栈的效率比较高。堆需要专门的函数库(malloc和free)来使用,内部有一定的算法,所以堆的效率比栈低的多,但堆可以动态分配内存空间
#include <stdio.h>
//堆和栈的差异
char* print_stack(){
char a[100]="I am print_stack func";
char *p;
p=a;
puts(p);
return p;
}
int main(){
char *p;
p=print_stack();
puts(p);
//每次去调用print_stack函数以后,当函数执行完了以后,函数的栈空间操作系统就把它释放掉了,就会给下一个函数使用,
// 也就是puts(栈空间释放,后面的空间被其他函数使用)
return 0;
}
主函数puts(p);出现乱码原因
每次调用print_stack函数以后,当函数执行完了以后,函数的栈空间操作系统就把它释放掉了,就会给下一个函数使用, 也就是puts(栈空间释放,后面的空间被其他函数使用),所以出现乱码
解决方法:用malloc申请空间
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//堆和栈的差异
char* print_stack(){
char a[100]="I am print_stack func";
char *p;
p=a;
puts(p);
return p;
}
char* print_malloc(){
char *p=(char*) malloc(100);//堆空间再整个进程中一直有效,不因为函数结束而消亡,需要自己free(),释放申请空间
strcpy(p,"i am print_malloc func");
puts(p);
return p;
}
int main(){
char *p;
p=print_stack();
puts(p);
//每次去调用print_stack函数以后,当函数执行完了以后,函数的栈空间操作系统就把它释放掉了,就会给下一个函数使用,
// 也就是puts(栈空间释放,后面的空间被其他函数使用)
p=print_malloc();
puts(p);
return 0;
}
- 堆空间再整个进程中一直有效,不因为函数结束而消亡,需要自己free(),释放申请空间