c语言—指针详解***内存地址***指针字节数***注意事项

Posted 天喜Studio

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c语言—指针详解***内存地址***指针字节数***注意事项相关的知识,希望对你有一定的参考价值。

创作不易,本篇文章如果帮助到了你,还请点赞支持一下♡>𖥦<)!! 

主页专栏有更多知识,如有疑问欢迎大家指正讨论,共同进步!

给大家跳段街舞感谢支持!ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ

目录

一、初步认识指针、内存地址的概念

1)内存地址

2)地址的相关运算

 二、指针变量

1)声明指针变量

 2)指针变量的字节数—根据操作系统的位数而不同

3)指针的移动

 三、多级指针

 四、指针注意事项

1)移动不越界

​2)定义指针不省 * 

3)初始空值可用NULL


一、初步认识指针、内存地址的概念

寻找地址的行为:想象成自己是快递小哥,送快递需要寻找地址,根据地址门牌号派送快递

类比到计算机寻找地址的过程,两个 编程思想:找地址,得空间! 

 

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语言—指针详解***内存地址***指针字节数***注意事项的主要内容,如果未能解决你的问题,请参考以下文章

C语言中的指针详解

C语言---指针变量详解1

c语言程序设计知识点总结03

[学习笔记]C语言中关于指针的详解1

C语言指针/引用/取值

C/C++ 常量指针,指针常量指向常量的常指针详解