c语言—指针详解***内存地址***指针字节数***注意事项
Posted 天喜Studio
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c语言—指针详解***内存地址***指针字节数***注意事项相关的知识,希望对你有一定的参考价值。
创作不易,本篇文章如果帮助到了你,还请点赞支持一下♡>𖥦<)!!
主页专栏有更多知识,如有疑问欢迎大家指正讨论,共同进步!
给大家跳段街舞感谢支持!ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ
目录
一、初步认识指针、内存地址的概念
寻找地址的行为:想象成自己是快递小哥,送快递需要寻找地址,根据地址门牌号派送快递
类比到计算机寻找地址的过程,两个 编程思想:找地址,得空间!
1)内存地址
内存地址即内存的地址,在创建变量时,计算机会分配一个内存空间用来存放变量,内存地址就是这个内存空间的地址,对每个字节来说都有自己的地址,在输出变量时,计算机就会找到这个变量的内存空间,从内存空间中取出变量。
可以将内存地址想象成我们居住的小区,住宅的最小单位是户(买房按户起卖,才不会卖几平米呢(╯°Д°)╯︵ ┻━┻),每户就是一个内存空间,每户都有门牌号,每户的门牌号就是内存空间的地址,门牌号具有唯一性,连续性,编号从小到大(101、102、201、202),内存地址也具有这些特征。(≧∇≦)ノ 很生动形象吧!
理解了内存地址,就引入了新的概念——指针。指针就是地址!!!
2)地址的相关运算
&取变量所占字节的首地址; * 根据地址取值 (&取地址 *对地址取空间)。
#include <stdio.h>
int main()
//地址相关运算:&取变量所占字节的首地址 * 根据地址取值 (&取地址 *对地址取空间)
int age = 65;
printf("十六进制地址:%p 十进制地址:%d\\n",&age,&age);
printf("age = %d\\n",age,*&age);//*和&优先级相同,结合方向从右向左 *&为互逆运算,结果仍为age
(*& age)++; //就是age++
return 0;
注:由于++运算符的优先级比较高,(*& age)++中需要加(),否则会先计算age++
小提醒✿:在内存中存入的数据为16位哦~
二、指针变量
1)声明指针变量
指针变量就是存储内存地址的变量。
//声明指针变量
int a = 5;
int* p; // *是指针的标志 int* 是一个组合类型——整型指针类型
p = &a; // a的地址被 p指针变量保留:p指向了a
printf("p(地址):%p\\n",p);
a--; // 利用指针对存储值进行算术运算(注意运算符的优先级)
a *= 2;
(*p)--; // a-- 和(*p)--相同 直接改变a为直接操作 通过*p取a空间改变a为间接操作
//注:*和--优先级相同 需要()
*p *= 2;
2)指针变量的字节数—根据操作系统的位数而不同
#include <stdio.h>
int main()
int a = 5;
int* p; // *是指针的标志 int* 是一个组合类型——整型指针类型
p = &a; // a的地址被 p指针变量保留:p指向了a
printf("p的字节数:%d int*的字节数:%d\\n", sizeof(p), sizeof(int*) );
char* pc;
double* pd;
unsigned long long* pull;
printf("pc的字节数:%d char*的字节数:%d\\n", sizeof(pc), sizeof(char*));
printf("pd的字节数:%d double*的字节数:%d\\n", sizeof(pd), sizeof(double*));
printf("pull的字节数:%d unsigned long long*的字节数:%d\\n", sizeof(pull),sizeof(unsigned long long*));
printf("同一个操作系统中,不论什么类型的指针变量,所占字节都相同(不论什么样的车,车牌号都是5位)\\n");
printf("只要是指针变量类型,就占4(32位)/8字节(64位)(地址编号大小)\\n");
return 0;
无论什么类型的指针变量,所占字节长度是固定的,因为指针变量保留的是内存地址的编号,它只能随 着32位系统或64位系统而不同 。32位就是用4个字节空间保留地址编号,64位就用8个字节空间保留地 址编号。
我们可以使用调试器进行观察:
3)指针的移动
指针的移动:根据数据类型不同,移动的步伐大小也不同
*p是一个计算过程 得到空间
p负责找到首地址(开头的小地址)
*负责:根据类型的字节数 获得空间使用权
#include <stdio.h>
int main()
int a = 5;
int* p; // *是指针的标志 int* 是一个组合类型——整型指针类型
p = &a; // a的地址被 p指针变量保留:p指向了a
char* pc = &a;
double* pd = &a;
unsigned long long* pull = &a;
printf("类型不同,得到空间使用权大小不同:\\n");
printf("\\t%d %d char* pc:%d个字节的空间使用权\\n", sizeof(pc), sizeof(char*),sizeof(*pc) );
printf("\\t%d %d double* pd:%d个字节的空间使用权\\n", sizeof(pd), sizeof(double*), sizeof(*pd) );
printf("\\t%d %d unsigned long long* pull:%d个字节的空间使用权\\n", sizeof(pull), sizeof(unsigned long long*), sizeof(*pull) );
printf("类型不同,偏移的字节数不同:\\n");
printf("\\tint* 4字节:%d %d %d %d\\n",p-1,p,p+1,p+2);
printf("\\tchar* 1字节:%d %d %d %d\\n",pc-1,pc,pc+1,pc+2);
printf("\\tdouble *8字节:%d %d %d %d\\n",pd-1,pd,pd+1,pd+2);
printf("\\tunsigned long long* 8字节:%d %d %d %d\\n",pull-1,pull,pull+1,pull+2);
return 0;
他们的开始地址都相同,由于数据类型的不同,偏移量不同
三、多级指针
多级指针又称为:指向指针的指针。 指针也是数据类型,也有他自己的内存地址,也有指向他的指针。 套娃呢搁这(*>.<*)
#include <stdio.h>
int main()
/*
多级指针又称为:指向指针的指针。
*/
int a = 100;
int* p = &a;
int** q = &p;//二级指针
int*** z = &q;
printf("%p %p %p\\n",p,q,z);
printf("%d\\n",***z);
return 0;
#include <stdio.h>
int main()
int a, b, c;//创建了3个int型变量
int* p, q, k;//创建了一个指针变量 2个int型变量
int* x, * y, * z;//创建了三个指针变量
return 0;
四、指针注意事项
1)移动不越界
指针不要位移到不属于本程序的内存空间,也不要利用指针改变不属于本程序内存空间的数据
int a = 5;
int* p = &a;
*p = 10;
p += 10;//偏移10位:不属于自己空间
*p = 8;
int* q = (int*)0x5823682;
*q = 8;
2)定义指针不省 *
声明多个指针变量类型时,*不能省略。
#include <stdio.h>
int main()
int a, b, c;//创建了3个int型变量
int* p, q, k;//创建了一个指针变量 2个int型变量
int* x, * y, * z;//创建了三个指针变量
return 0;
调试器看一眼:
y,z前没有* ,没有被当成指针变量
3)初始空值可用NULL
指针变量的初始值如果没有明确指向目标用NULL赋值
#include <stdio.h>
int main()
int z = 0;
double t = 0.0;
int* x = NULL;//指针变量不确定指向哪个空间 建议用NULL设置:空地址
return 0;
C指针
计算机中所有的数据都必须放在内存中,以二进制的形式存储在内存中,才能被CPU所使用。不同类型的数据占用的字节数不一样,为了正确地访问这些数据,必须为每个字节都编上号码。将内存中字节的编号称为地址(Address)或指针(Pointer)。地址从 0 开始依次增加,对于 32 位环境,程序能够使用的内存为 4GB,最小的地址为 0,最大的地址为 0XFFFFFFFF。
计算机无法从格式上区分某块内存到底存储的是数据还是代码。当程序被加载到内存后,操作系统会给不同的内存块指定不同的权限,权限中包含执行权限的内存块就是代码,否则是数据。
CPU 只能通过地址来取得内存中的代码和数据,程序在执行过程中会告知 CPU 要执行的代码以及要读写的数据的地址。如果程序不小心出错,或者开发者有意为之,在 CPU 要写入数据时给它一个代码区域的地址,就会发生内存访问错误。这种内存访问错误会被硬件和操作系统拦截,强制程序崩溃,程序员没有挽救的机会。CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。
1、指针变量
若果一个变量存储了一份数据的指针,我们就称它为指针变量,指针变量的值就是某份数据的地址。
1.1 定义
定义指针变量和定义普通变量类似,不过需要在变量名前加*号。*表示其实一个指针变量,类型表示该指针变量所指向的数据的类型 。指针变量也可以被多次写入,随时都能够改变指针变量的值。定义指针变量时必须带*
,给指针变量赋值时不能带*
。指针变量也可以连续定义,但是要注意每个变量前面都要带*。
1.2 取值
指针变量存储了数据的地址,通过指针变量能够获得该地址上的数据,格式为:*pointer; *在此处
称为指针运算符,用来取得某个地址上的数据。普通变量只需一次即可取到数据,指针变量需要2次,因为其内存中存的是地址,还需要根据地址去取数据,即使用指针是间接获取数据,使用变量名是直接获取数据,前者比后者的代价要高。
*
在不同的场景下有不同的作用:*
可以用在指针变量的定义中,表明这是一个指针变量,以和普通变量区分开;使用指针变量时在前面加*
表示获取指针指向的数据,或者说表示的是指针指向的数据本身。指针变量定义时初始化,可使用*pointer取得数据,给指针变量本身赋值时不能加*。
1.2.1 关于*和&
若有一个 int 类型的变量 a,pa 是指向它的指针,即int a = 12;int* pa = &a;那么*&a
和&*pa的含义是:
*&a
可以理解为*(&a)
,&a
表示取变量 a 的地址(等价于 pa),*(&a)
表示取这个地址上的数据(等价于 *pa),即*&a
仍然等价于 a。 &*pa
可以理解为&(*pa)
,*pa
表示取得 pa 指向的数据(等价于 a),&(*pa)
表示数据的地址(等价于 &a),所以&*pa
等价于 pa。
1.2.2 指针变量的运算
对于指向普通变量的指针,我们往往不进行加减运算,虽然编译器并不会报错,但这样做没有意义,因为没有规定变量的存储方式,如果连续定义多个变量,它们有可能是挨着的,也有可能是分散的,这取决于变量的类型、编译器的实现以及具体的编译模式,这样也就不知道在这个变量地址的后面是否有数据。因此不要尝试通过指针获取下一个变量的地址,如*(p+i),获取到的数据有可能并不知道你想要的的。
当对指针变量进行比较运算时,比较的是指针变量本身的值,也就是数据的地址。如果地址相等,那么两个指针就指向同一份数据,否则就指向不同的数据。最后对于普通变量而言,最好不要对指针变量进行乘法、除法、取余等其他运算,除了会发生语法错误,也没有实际的含义。
2、数组指针
定义数组时,要给出数组名和数组长度,数组名可以认为是一个指针,它指向数组的第 0 个元素。数组名和数组首地址并不总是等价,可以说“被转换成了一个指针”。对于数组而言,可以直接将数组名赋值给指针变量 。若一个指针指向了数组,就称它为数组指针(Array Pointer)。数组指针指向的是数组中的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关。
1 int arr[] = { 99, 15, 100, 888, 252 };
2 int *p = arr;
数组指针可以使用两种方法来访问数组元素,一种是使用下标,另外一种是使用指针。不同的是,数组名是常量,它的值不能改变,而数组指针是变量(除非特别指明它是常量),它的值可以任意改变。也就是说,数组名只能指向数组的开头,而数组指针可以先指向数组开头,再指向其他元素。
2.1 数组指针使用含义
若p 是指向数组 arr 中第 n 个元素的指针, *p++、*++p、(*p)++ 的含义是:
*p++ 等价于 *(p++),表示先取得第 n 个元素的值,再将 p 指向下一个元素。
*++p 等价于 *(++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会获得第 n+1 个数组元素的值。
(*p)++ ,会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 0 个元素,并且第 0 个元素的值为 99,执行完该语句后,第 0 个元素的值就会变为 100。
3、指针数组
若一个数组中的所有元素保存的都是指针,那么我们就称它为指针数组。指针数组的定义形式一般为:
dataType *arrayName[length];
[ ]
的优先级高于*
,该定义形式应该理解为:
dataType *(arrayName[length]);
若存放的是字符串,需要注意的是:字符数组 str 中存放的是字符串的首地址,不是字符串本身,字符串本身位于其他的内存区域,和字符数组是分开的。
4、二维数组指针
1 int (*p)[4] = a;
括号中的*
表明 p 是一个指针,它指向一个数组,数组的类型为int [4]
,这正是 a 所包含的每个一维数组的类型。[ ]
的优先级高于*
,( )
是必须要加的,否则就是指针数组。对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的数据类型有关,p 指向的数据类型是int [4]。p+1就是前进 4×4 = 16 个字节。
根据定义可以知道:p
指向数组 a 的开头,也即第 0 行;p+1
前进一行,指向第 1 行;*(p+1)
表示取地址上的数据,也就是整个第 1 行数据。
4.1 表达式含义
*(p+1)+1
表示第 1 行第 1 个元素的地址:*(p+1)
单独使用时表示的是第 1 行数据,放在表达式中会被转换为第 1 行数据的首地址,也就是第 1 行第 0 个元素的地址,因为使用整行数据没有实际的含义,编译器遇到这种情况都会转换为指向该行第 0 个元素的指针。
*(*(p+1)+1)
表示第 1 行第 1 个元素的值:*(p+1)+1表示第 1 行第 1 个元素的地址,加上*表示取地址中的值。
可知:
1 a+i == p+i
2 a[i] == p[i] == *(a+i) == *(p+i)
3 a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j) == *(*(p+i)+j)
4.2 指针数组和二维数组指针的区别
定义如下:
1 int *(p1[5]); //指针数组,可以去掉括号直接写作 int *p1[5];
2 int (*p2)[5]; //二维数组指针,不能去掉括号
指针数组是一个数组,只是每个元素保存的都是指针。二维数组指针是一个指针,它指向一个二维数组。
5、字符串指针
2种方法:定义字符串数组,再赋值给指针变量;直接使用一个指针指向字符串。
1 //使用*(str+i)
2 for(i=0; i<len; i++){
3 printf("%c", *(str+i));
4 }
5 printf("
");
6 //使用str[i]
7 for(i=0; i<len; i++){
8 printf("%c", str[i]);
9 }
使用指针变量输出字符串和下标输出的区别:在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,第二种形式的字符串存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。内存权限的不同导致的一个明显结果就是,字符数组在定义后可以读取和修改每个字符,而对于第二种形式的字符串,一旦被定义后就只能读取不能修改,任何对它的赋值都是错误的。
6、指针变量作为函数参数
用指针变量作函数参数可以将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,并且这些数据不会随着函数的结束而被销毁。
1 max(int *intArr, int len)
数组是一系列数据的集合,无法通过参数将它们一次性传递到函数内部,如果希望在函数内部操作数组,必须传递数组指针。参数的传递本质上是一次赋值的过程,赋值就是对内存进行拷贝。所谓内存拷贝,是指将一块内存上的数据复制到另一块内存上。但数组是一系列数据的集合,数据的数量没有限制,可能很少,也可能成千上万,对它们进行内存拷贝有可能是一个漫长的过程,会严重拖慢程序的效率。需要强调的是:不管使用哪种方式传递数组,都不能在函数内部求得数组长度,因为 intArr 仅仅是一个指针,而不是真正的数组,所以必须要额外增加一个参数来传递数组长度。
7、指针作为函数返回值
允许函数的返回值是一个指针(地址),这样的函数称为指针函数。用指针作为函数返回值时需要注意的一点是,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针请尽量不要指向这些数据,C语言没有任何机制来保证这些数据会一直有效,它们在后续使用过程中可能会引发运行时错误。也就是所谓的销毁并不是将局部数据所占用的内存全部抹掉,而是程序放弃对它的使用权限,弃之不理,后面的代码可以随意使用这块内存。
8、函数指针
函数指针的定义形式为:returnType (*pointerName)(param list);
注意( )
的优先级高于*
,第一个括号不能省略,如果写作returnType *pointerName(param list);
就成了函数原型,它表明函数的返回值类型为returnType *
。
8.1 使用指针实现对函数的调用
1 //定义函数指针 max是一个函数
2 int (*pmax)(int, int) = max; //也可以写作int (*pmax)(int a, int b)
3 ...
4 maxval = (*pmax)(x, y);
pmax 是一个函数指针,在前面加 * 就表示对它指向的函数进行调用。注意( )
的优先级高于*
,第一个括号不能省略。
9、二级指针
若一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。指针变量也是一种变量,也会占用存储空间,也可以使用&
获取它的地址。获取指针指向的数据时,一级指针加一个*
,二级指针加两个*,类推其他级别指针。
1 int a =100;
2 int *p1 = &a;
3 int **p2 = &p1;
4 int ***p3 = &p2;
5 printf("%d, %d, %d, %d
", a, *p1, **p2, ***p3);
6 printf("&p2 = %#X, p3 = %#X
", &p2, p3);
7 printf("&p1 = %#X, p2 = %#X, *p3 = %#X
", &p1, p2, *p3);
8 printf(" &a = %#X, p1 = %#X, *p2 = %#X, **p3 = %#X
", &a, p1, *p2, **p3);
以上每行输出的值是一样的,因为指向一样的地址。
方框里面是变量本身的值,方框下面是变量的地址。
10、总结
程序在运行过程中需要的是数据和指令的地址,程序被编译和链接后,变量名、函数名等都会消失,取而代之的是它们对应的地址。
10.1 常见指针变量的定义
定 义 | 含 义 |
---|---|
int *p; | p 可以指向 int 类型的数据,也可以指向类似 int arr[n] 的数组。 |
int **p; | p 为二级指针,指向 int * 类型的数据。 |
int *p[n]; | p 为指针数组。[ ] 的优先级高于 *,所以应该理解为 int *(p[n]); |
int (*p)[n]; | p 为二维数组指针。 |
int *p(); | p 是一个函数,它的返回值类型为 int *。 |
int (*p)(); | p 是一个函数指针,指向原型为 int func() 的函数。 |
10.2 使用
1)指针变量可以进行加减运算,并不是简单的加上或减去一个整数,而是跟指针指向的数据类型有关
2)给指针变量赋值时,要将一份数据的地址赋给它,不能直接赋给一个整数
3)使用指针变量之前一定要初始化,对于暂时没有指向的指针,建议赋值NULL
4)两个指针变量可以相减。如果两个指针变量指向同一个数组中的某个元素,那么相减的结果就是两个指针之间相差的元素个数
5)数组也是有类型的,数组名的本意是表示一组类型相同的数据。在定义数组时,或者和 sizeof、& 运算符一起使用时数组名才表示整个数组,表达式中的数组名会被转换为一个指向数组的指针
以上是关于c语言—指针详解***内存地址***指针字节数***注意事项的主要内容,如果未能解决你的问题,请参考以下文章