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; }
复制代码

 

 


__EOF__

本文作者halfup
本文链接https://www.cnblogs.com/halfup/p/15791310.html
关于博主:编程小萌新一名,希望从今天开始慢慢提高,一步步走向技术的高峰!
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   halfup  阅读(270)  评论(4编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
点击右上角即可分享
微信分享提示