C语言学习--指针
指针的的定义:
- 使用指针的需求 将某地址保存下来
- 指针使用的场景 传递与偏移
就是保存地址和传递参数
#include <stdio.h> int main() { int i=5; int* i_pointer=&i; }
指针变量 int 型只能对应 int 变量(啥型号对应啥)
指针变量用来保存地址 取变量地址
指针的定义格式如下
基类型 * 指针变量名
前面是类型,后面是变量名
基本这样传递就可以了,然后*取值
关于 * 和 & 的一些疑问?
如果已经执行了
pointer_1=&a ,给地址了
那么 &* pointer_1的含义是什么?
& 与 * 的优先级相同,但要按照从右往左的顺序
首先* pointer_1取值 ,相当于* &a
取出来变量a的值(5),所以后面 & a(5)
&* pointer_1 等价与 &a 都是 取地址
那么 *& a的含义是什么?
首先&a 取出地址 ,也就是 pointer_1一样
在进行 *取值运算,取出来变量a(5)
*&a 与 a 等价, 都是 取值
- 总结:
*
取值(解引用)&
取地址 (引用)
对于数组指针
比如
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> int main() { int a[5]={1,2,3,4,5}; int* p;//对一个指针变量进行取值,结果也是其基类型(这里是int) p=a; //刚开始是首地址 printf("%d\n",*p); getchar(); system("pause"); return 0; }
这种情况
这时候单指针指的是数组的起始位置 就是a[0]
如何想打印的话,使用循环
for (int i = 0; i < 5; i++) { printf("%d\n",*(p+i)); }
注意要保证二边类型一样:a[]数组 p=a a也是数组的初始位置
如果&a 就是取a数组的初始地址a[1] ,再取一次地址
所以不能用 p=&a
指针自增自减
以前已经见过i++ 和 ++i 等
那么要是 *p++ ,*p-- *++p 这些呢
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> int main() { int a[3]={2,7,8}; int *p; int j; p=a;//让指针变量p指向数组的开头 j=(*p)++;// printf("a[0]=%d,j=%d,*p=%d\n",a[0],j,*p); getchar(); system("pause"); return 0; }
试着说出 a[0] *p 和 j 的值
解答:先是(*p) 访问到2数组起始的空间
整体 j (*p)++变化 指向下一个空间 值,即数字取值3的空间
这时候数组初始位置 地址不变,这里面的值变为3
所以 a[0] 为3 *p整体变下来为3
这时候 说下j 的值 ,先看 如果 j=i++ ; i=1;结束后j等于几?
作者:顺其自然的活着1、首先,单独拿出来说++i和i++,意思都是一样的,就是i=i+1。
2、如果当做运算符来说,就是a=i++或者a=++i这样的形式。情况就不一样了。
先说a=i++,这个运算的意思是先把i的值赋予a,然后在执行i=i+1;
而a=++i,这个的意思是先执行i=i+1,然后在把i的值赋予a;
举个例子来说,如果一开始i=4。
那么执行a=i++这条语句之后,a=4,i=5;
那么执行a=++i这条语句之后,i=5,a=5;
同理,i--和--i的用法也是一样的。
链接:https://www.zhihu.com/question/19811087/answer/207494860
就是单独拉出来就是+1,做运算符 得化
j=i++; 先进行赋值, j=1; 再i++=2; i=2;
j=++i; 先i+1 ;i=2;再进行 赋值, j=2;
所以上面代码的j值
j=(*p)++;
(*p)++ 取值3 地址数组初始位置
但是j= 先赋值 j=(*P)= 刚开始就赋值,所以为 *p =2
后面再运算 ++
*p才是3
带着把数组a[0]地址的值换了
指针与一维数组
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> void change(char d[]) { d[0]; } int main() { char c[10]="hello"; change(c);
getchar(); system("pause"); return 0; }
比如这种 函数调用就是
d[] 其实与 *d 一样 都是i指针数组
但是没做这种d[] 这种,所以函数的形参里面应该不能放 数组
放个指针变量就行
void change (char *d)
拿不到数组长度
void change(char *d) { *d='H'; d[1]='E'; *(d+2)='L' }
这种都可以改变值
d[1]就是 c[1]传给函数的
直接把c的地址传给函数形参指针d
等于d=c,地址相同
动态内存申请malloc
申请5*sizeof(int)=20字节
数组一开始定义好就确定下来了,数据是放在栈空间
进程地址空间:堆 栈
解释:栈空间的大小在编译时是确定的。如果使用的空间大小不确定,那么就要使用堆空间。
int i; //申请多大的空间 scanf("%d",&i); char* p; p=(char*) malloc(i); //malloc动态申请空间 //malloc申请空间的单位是字节 //malloc返回的是void*类型指针 //(char*) 强转 int *p1; p1=(int*)malloc(20);//malloc 申请的是字节,这里申请了20字节 //1个int 占4个字节,所以等于申请了5个int,等于a[0]-a[4]
总申请代码:
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int i; //申请多大的空间 scanf("%d",&i); char* p; p=(char*) malloc(i); //malloc动态申请空间 //malloc申请空间的单位是字节 //malloc返回的是void*类型指针 //(char*) 强转 strcpy(p,"malloc success"); puts(p); getchar(); system("pause"); return 0; }
申请100个字节放 "malloc success" ,如图:
但是还要free 释放内存
这里记录下指针p偏移错误
int i; //申请多大的空间 scanf("%d",&i); char* p; p=(char*) malloc(i); //malloc动态申请空间 p++; strcpy(p,"malloc success");
puts(p);
这里p++只想打印后面的 alloc success 但是这样free(p)会报错
可以在写一个 *p1
char* p; char *p1; p=(char*) malloc(i); //malloc动态申请空间 //malloc申请空间的单位是字节 //malloc返回的是void*类型指针 //(char*) 强转 p1++; strcpy(p,"malloc success"); strcpy(p1,p); puts(p); free(p);
最后
free(p) ; p=NULL ;
释放操作
释放后,这时候p是野指针,就没有指向的
所以要 p=NULL;
如果不把p值为NULL,被称为野指针
注意:堆空间不会随子函数的结束而释放,必须自己free
栈空间与堆空间差异
栈空间:
看一个例子:
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> char* printf_stack() //char* 返回char* 类型 { char c[17]="i am print_stack"; puts(c); //能正常打印 return c; //返回 char* c } int main() { char* p; p=printf_stack();//p=c; puts(p); getchar(); system("pause"); return 0; }
函数里面的正常打印,下面main函数里面的却不能正常打印
原因是上面是栈(自动分配空间) 函数结束后会自动释放
打印出来不正常
因为puts(p)这时候占用了栈空间
堆空间:
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> char* printf_stack() //char* 返回char* 类型 { char c[17]="i am print_stack"; puts(c); //能正常打印 return c; //返回 char* c } char* printf_malloc() //char* 返回char* 类型 { char* p=(char*)malloc(30);//申请堆空间内存 strcpy(p,"I am print_malloc"); puts(p); return p; //返回 char* p } int main() { char* p; p=printf_stack(); //puts(p); p=printf_malloc(); puts(p); getchar(); system("pause"); return 0; }
这是因为:申请堆空间不会因为函数结束结束,除非free否则一直能用
来做个例子吧:
输入一个整型数,然后申请对应大小空间内存,然后读取一个字符串,字符串的输入长度小于最初输入的整型数大小,最后输出输入的字符串即可(无需考虑输入的字符串过长,超过了内存大小);
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int n; //申请多大的空间 scanf("%d",&n); char* p; p=(char*)malloc(n); char c; scanf("%c",&c); gets(p); puts(p); getchar(); system("pause"); return 0; }
主要讲下这里 char c; scanf("%c",&c);’
puts和 gets用的都是缓冲区
因为前面有scanf("%d",i) ,scanf在缓冲区里里面输入一个 数字
比如 10\n \n是结束符
此时缓冲区为
scanf读取后 \n还在缓冲区里面
所以 char c; scanf("%c",&c); 是为了去除缓冲区里面的\n
字符指针与字符数组的初始化
#include <string.h> int main() { char* p="hello";//把字符串型常量"hello"的首地址赋给p char c[10]="hello";//等价与strcpy(c,"hello"); //c[0]='H'; printf("c[0]=&c\n",c[0]); printf("p[0]=&c\n",p[0]); //p[0]='H'; //不可以对常量区数据进行修改 //p="world";//将字符串world的地址赋给p //c="world";//非法 getchar(); system("pause"); return 0; }
//p[0]='H'; //不可以对常量区数据进行修改
内存是有权限的: r(可读) ,w(可写), rw
这时候p[0]的地址里面内存不可写
字符串 常量存在 数据区,不是堆也不是栈,需要内存申请读写
char c[10] =“hello world”
把字符串变量 hello world 从数据区拿出来到栈
所以是可读可写的
这样 c[0]可以从 数据区拿'H'
而指针p[0]:
p[0]='H'; //不可以对常量区数据进行修改
这时候‘H’,在数据区,不能读取变量,也不能取起始地址,
再来看
p="world";//将字符串world的地址赋给p
”world“ 是字符串型 数据区 起始地址有
这样可以把它的起始地址赋给p
//c="world";//非法
c是数组,里面的地址不会更改,不能直接赋值
二级指针
-
二级指针只服务于一级指针的传递与偏移
-
要想在子函数中改变一个变量的值,必须把该变量的地址传进去
-
要想在子函数中改变一个指针变量的值,必须把该指针变量的地址传进去
类似这样,二级指针就是 p是一个指针, **p2=&p ;把p的原来的地址取过来
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> void change(int** p,int *pj) { *p=pj; //*p等价于pi } 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);//等于10,10,5 change(&pi,pj); printf("after change i=%d,*pi=%d,*pj=%d\n",i,*pi,*pj); //目标是让*pi 的值为5 getchar(); system("pause"); return 0; }