龙空技术网

C语言指针

孙工精品 143

前言:

眼前我们对“c语言中地址是多少位的”都比较注意,各位老铁们都需要分析一些“c语言中地址是多少位的”的相关文章。那么小编也在网摘上收集了一些关于“c语言中地址是多少位的””的相关文章,希望我们能喜欢,你们快快来学习一下吧!

1、指针的理解与定义1.1 变量的访问方式

计算机中程序的运行都是在内存中进行的,变量也是在内存中分配的空间,且不同类型的变量占用不同大小的空间。那如何访问内存中变量存储的数据呢?有两种方式:直接访问间接访问。直接访问,直接使用变量名进行的访问,以前的程序中都是采用这种方式。

 int num1 = 10; int num2 = 20; int num3 = num1 + num2;

间接访问,通过指针来实现。下面看如何理解指针。

1.2 内存地址与指针

为了能够有效的访问到内存的每个单元(即一个字节),就给内存单元进行了编号,这些编号被称为该内存单元的地址。因为每个内存单元都有地址,所以变量存储的数据也是有地址的。

 int num = 5;

通过地址能找到所需的变量单元,可以说,地址指向该变量单元,将地址形象化地称为“指针”。即:

变量:命名的内存空间,用于存放各种类型的数据。变量名:变量名是给内存空间取的一个容易记忆的名字。变量值:在变量单元中存放的数据值。变量的地址:变量所使用的内存空间的地址,即指针指针变量:一个变量专门用来存放另一变量在内存中数据的地址 (即指针),则它称为“指针变量”。我们可以通过访问指针变量达到访问内存中另一个变量数据的目的。(有时为了阐述方便,将指针变量直接说成指针。)

上图中,地址0x00000001是变量 i 的指针,i_pointer就是一个指针变量。

体会:指针就是内存地址,使用指针访问变量,就是直接对内存地址中的数据进行操作。

1.3 指针变量的定义

一般格式:

 数据类型 *指针变量名 [=初始地址值];
数据类型是指针变量所指向变量数据类型。可以是 int、char、float 等基本类型,也可以是数组等构造类型。字符 * 用于告知系统这里定义的是一个指针变量,通常跟在类型关键字的后面。比如, char * 表示一个指向字符的指针, float * 表示一个指向 float 类型的值的指针。此外,还有指向数组的指针、指向结构体的指针。

举例1:

 int *p;  //读作:指向int的指针”或简称“int指针”

这是一个指针变量,用于存储int型的整数在内存空间中数据的地址。

变形写法:

 int* p; int * p;

注意:

1、指针变量的名字是 p,不是*p。

2、指针变量中只能存放地址,不要将一个整数(或任何其它非地址类型的数据)赋给一个指针变量。

举例2:同一行声明两个指针变量

 // 正确 int * a, * b; // 错误 int* a, b;   //此时a是整数指针变量,而b是整数变量

举例3:一个指针指向的可能还是指针,这时就要用两个星号 ** 表示。(后面讲)

 int **foo;
1.4 指针的应用场景

场景1:使用指针访问变量或数组的元素。

场景2:应用在数据结构中。比如:

2、指针的运算

指针作为一种特殊的数据类型可以参与运算,但与其他数据类型不同的是,指针的运算都是针对内存中的地址来实现的。

2.1 取址运算符:&

取址运算符,使用“&”符号来表示。作用:取出指定变量在内存中的地址,其语法格式如下:

 &变量

举例1:

 int num = 10;  printf("num = %d\n", num); // 输出变量的值。 num = 10 printf("&num = %p\n", &num); // 输出变量的内存地址。&num = 00000050593ffbbc

说明:

1、在输出取址运算获得的地址时,需要使用“%p”作为格式输出符。

2、这里num的4个字节,每个字节都有地址,取出的是第一个字节的地址(较小的地址)。

举例2:将变量的地址赋值给指针变量

 int num = 10; int *p; //p为一个整型指针变量 p = # 

举例3:

 int d = 10; int *e, *f; e = &d; f = e;

指针变量的赋值

1、指针变量中只能存放地址(指针),不要将一个整数(或任何其它非地址类型的数据)赋给一个指针变量。

2、C语言中的地址包括位置信息(内存编号,或称纯地址)和它所指向的数据的类型信息,即它是“带类型的地址”。所以,一个指针变量只能指向同一个类型的变量,不能抛开类型随意赋值。

char* 类型的指针是为了存放 char 类型变量的地址。short* 类型的指针是为了存放 short 类型变量的地址。int* 类型的指针是为了存放 int 类型变量的地址。

3、在没有对指针变量赋值时,指针变量的值是不确定的,可能系统会分配一个未知的地址,此时使用此指针变量可能会导致不可预料的后果甚至是系统崩溃。为了避免这个问题,通常给指针变量赋初始值为0(或NULL),并把值为0的指针变量称为空指针变量

举例4:通过指针变量修改指向的内存中的数据

 int main() {      int num = 10, *ptr;     ptr = #     printf("%d\n",num);      scanf("%d", ptr); //等价于scanf("%d", &num);          printf("%d\n",num);      return 0; }
2.2 取值运算符:*

在C语言中针对指针运算还提供了一个取值运算符,使用“*”符号表示。其作用与&相反,根据一个给定的内存地址取出该地址对应变量的值。也称为解引用符号。其格式如下:

 *指针表达式

其中,“*”不同于定义指针变量的符号,这里是运算符。“指针表达式”用于得到一个内存地址,与“*”结合以获得该内存地址对应变量的值。

举例1:

 int main() {      int a = 2024;     int *p;     p = &a;          printf("%p\n",&a); //0000005cc43ff6d4     printf("%p\n",p);  //0000005cc43ff6d4     printf("%d\n", *p); //2024          return 0; }

举例2:

 int main() {      int num = 10; //这里定义一个整型变量num     printf("num = %d\n", num); //输出变量num的值。输出:num = 10     printf("&num = %p\n", &num); //输出变量num的地址。输出:&num = 000000e6a11ffa1c          int *p = #     printf("%p\n",p); //000000e6a11ffa1c     printf("%d\n",*p);//10          printf("*&num = %d\n", *&num);//通过num地址读取num中的数据。输出:*&num = 10          return 0; }

& 运算符与 * 运算符互为逆运算,下面的表达式总是成立:

int i = 5;

if (i == *(&i)) // 正确

举例3:通过指针变量修改指向内存地址位置上的值

 int main() {      int num = 10;     int *p = #     *p = 20;     printf("num = %d\n",num);  //num = 20      char ch = 'w';     char* pc = &ch;     *pc = 's';     printf("ch = %c\n", ch); //ch = 's'      return 0; }

举例4:

定义指针变量 p1、p2,默认各自指向整数a、b,a、b从键盘输入。设计程序,使得 p1 指向其中的较大值,p2 指向其中的较小值 。

 int main() {      int *p1, *p2, *p, a, b;     printf("请输入两个整数: ");     scanf("%d,%d", &a, &b);     p1 = &a;     p2 = &b;     if (a < b) {         p = p1;         p1 = p2;         p2 = p;     }      printf("输出p1、p2: ");     printf("%d,%d\n", *p1, *p2);      return 0; }

举例5:已有代码如下:

 int a = 10; int *p; p = &a;

请看问题:

问题1:&*p的含义是什么?

“&”“*”两个运算符的优先级别相同,但按自右而左方向运算。因此,&*p&a相同,即变量a的地址。如果有p1 = &*p;它的作用是将&a (a的地址)赋给p1 ,如果p1原来指向 b,经过重新赋值后它已不再指向b了,而指向了a。

问题2:*&a的含义是什么?

先进行&a运算,得a的地址,再进行*运算。*&a*p的作用是一样的,它们都等价于变量a。即*&a与 a 等价。2.3 指针的常用运算

指针本质上就是一个无符号整数,代表了内存地址。除了上面提到的取址运算外,指针还可以与整数加减、自增自减、同类指针相减运算等。但是规则并不是整数运算的规则。

2.3.1 指针与整数值的加减运算

格式:指针±整数

指针与整数值的加减运算,表示指针所指向的内存地址的移动(加,向后移动;减,向前移动)。指针移动的单位,与指针指向的数据类型有关。数据类型占据多少个字节,每单位就移动多少个字节。

通过此操作,可以快速定位你要的地址。

 short *s; s = (short *) 0x1234; printf("%hx\n", s + 1); //0x1236   复习:%hx :十六进制 short int 类型 printf("%hx\n", s - 1); //0x1232  int *i; i = (int *) 0x1234; printf("%x\n", i + 1); //0x1238    复习:%x :十六进制整数

说明:s + 1 表示指针向内存地址的高位移动一个单位,而一个单位的 short 类型占据两个字节的宽度,所以相当于向高位移动两个字节。

再比如:变量a、b、c、d和e都是整型数据int类型,它们在内存中占据一块连续的存储区域。指针变量p指向变量a,也就是p的值是0xFF12,则:

说明:指针p+1并不是地址+1,而是指针p指向数组中的下一个数据。比如,int *p,p+1表示当前地址+4,指向下一个整型数据。

举例1:

 int main() {      int arr[5] = {1, 2, 3, 4, 5};     int *p = &arr[1];     printf("p的地址为:%p,对应的值为%d\n", p, *p); //p1的地址为:000000df21bff6e4,对应的值为2     printf("p+1=的地址为:%p,对应的值为%d\n", p + 1, *(p + 1)); //p1+1=的地址为:000000df21bff6e8,对应的值为3     printf("p-1=的地址为:%p,对应的值为%d\n", p - 1, *(p - 1)); //p1-1=的地址为:000000df21bff6e0,对应的值为1      return 0; }

注意:只有指向连续的同类型数据区域,指针加、减整数才有实际意义。

举例2:

对于长度是 N 的一维数组 a,当使用指针 p 指向其首元素后,即可通过指针 p 访问数组的各个元素。

其中:

a[0]*p 表示a[1]*(p+1)表示a[i]*(p+i)表示

遍历数组操作如下:

 #include <stdio.h> #define LENGTH 5  int main() {      int arr[LENGTH] = {10,20,30,40,50};      //方式1:传统直接访问的方式     for(int i = 0;i < LENGTH;i++){         printf("%d ",arr[i]);     }      printf("\n");      //方式2:使用指针访问     int *p = &arr[0];     for(int i = 0;i < LENGTH;i++){         printf("%d ",*(p+i));     }      return 0; }
2.3.2 指针的自增、自减运算

指针类型变量也可以进行自增或自减运算,如下:

 p++ 、 p-- 、 ++p 、--p

++和--在运算符章节已经讲过,这里针对指针的增加或减少指的是内存地址的向前或向后移动。

针对于数组来说,由于数组在内存中是连续分布的。

当对指针进行++时,指针会按照它指向的数据类型字节数大小增加,比如 int * 指针,每 ++ 一次, 就增加4个字节。当对指针进行--时,指针会按照它指向的数据类型字节数大小减少,比如 int * 指针,每 -- 一次, 就减少4个字节。

举例1:

 int main() {     int arr[5] = {1, 2, 3, 4, 5};     int *p1 = &arr[0];     int *p2 = &arr[3];     printf("p1的值为:%d\n", *p1);        //1     printf("++p1的值为:%d\n", *(++p1));  //2     printf("p1的值为:%d\n", *p1);        //2      printf("p1的地址为:%p\n", p1);      //00000055c0bff704     printf("p1++的地址为:%p\n", ++p1);  //00000055c0bff708       printf("p2的值为:%d\n", *p2);       //4     printf("--p2的值为:%d\n", *(--p2)); //3     printf("p2的值为:%d\n", *p2);       //3      return 0; }

举例2:请分析下面几种情况。

初始情况:

 int a[5] = {10,20,30,40,50};

情况1:

 int *p = a;  //p开始时指向数组a的首元素    等同于 int *p = &a[0];  p++; //使p指向下一元素a[1] printf("%d\n",*p); //得到下一个元素a[1]的值,即20

情况2:

 int *p = a;  //p开始时指向数组a的首元素  printf("%d\n",*p++); //10   分析:由于++和*同优先级,结合方向自右而左,因此它等价于*(p++) printf("%d\n",*p);   //20

拓展:

 *(p++); //先取*p值,然后使p自增1 *(++p); //先使p自增1,再取*p

拓展:如果 p 当前指向 a 数组中第 i 个元素a[i],则:

 *(p--) //相当于a[i--],先对p进行“*”运算,再使p自减 *(++p) //相当于a[++i],先使p自加,再进行“*”运算 *(--p) //相当于a[--i],先使p自减,再进行“*”运算

情况3:

 int *p = &a[2];  //p开始时指向数组a的第3个元素 printf("%d\n",*(p--)); //30      p = &a[2]; printf("%d\n",*(++p)); //40  p = &a[2]; printf("%d\n",*(--p)); //20

情况3:

 int *p = a;            //p开始时指向数组a的首元素 printf("%d\n",++(*p)); //11  /* 分析:表示p所指向的元素值加1,如果p=a, 则相当于++a[0],若a[0]的值为10,则a[0]的值为11。 注意: 是元素a[0]的值加1,而不是指针p的值加1 */
2.3.3 同类指针相减运算

格式:指针 - 指针

相同类型的指针允许进行减法运算,返回它们之间的距离,即相隔多少个数据单位(注意:非字节数)。高位地址减去低位地址,返回的是正值;低位地址减去高位地址,返回的是负值。

返回的值属于 ptrdiff_t 类型,这是一个带符号的整数类型别名,具体类型根据系统不同而不同。这个类型的原型定义在头文件 stddef.h 里面。

举例1:

 int main() {      short *ps1;     short *ps2;     ps1 = (short *) 0x1234;     ps2 = (short *) 0x1236;     ptrdiff_t dist = ps2 - ps1;     printf("%d\n", dist); // 1   相差2个字节正好存放1个 short 类型的值。      int *pi1;     int *pi2;      pi1 = (int *) 0x1234;     pi2 = (int *) 0x1244;      ptrdiff_t dist1 = pi2 - pi1;     printf("%d\n",dist1);  //4   相差16个字节正好存放4个 int 类型的值。      return 0; }

举例2:

 int main() {     int arr[5] = {1, 2, 3, 4, 5};     int *p1 = &arr[0];     int *p2 = &arr[3];     printf("p1的地址为:%d\n", p1); //497022544     printf("p2的地址为:%d\n", p2); //497022556     printf("p2-p1=%d\n", p2 - p1); //3 等同于 (497022556 - 497022544)/4 ==> 3      return 0; }

体会:两个指针相减,通常两个指针都是指向同一数组中的元素才有意义。结果是两个地址之差除以数组元素的长度。不相干的两个变量的地址,通常没有做减法的必要。

举例:

 int main() {      int i = 10;     int j = 20;     int *p1 = &i;     int *p2 = &j;      ptrdiff_t dist = p1 - p2;     printf("%d\n",dist);   //通常没有计算减法的必要      return 0; }

非法:同类指针相加运算

两个指针进行加法是非法的,所得结果是没有意义的。

 int i = 10,j = 20; int *p1 = &i; int *p2 = &j; int *p3 = p1 + p2; //非法
2.3.4 指针间的比较运算

指针之间的比较运算,比如 ==、!= 、<、 <= 、 >、 >=。比较的是各自的内存地址的大小,返回值是整数 1 (true)或 0 (false)。

举例:

 int arr[5] = {1, 2, 3, 4, 5}; int *p1 = &arr[0]; int *p2 = &arr[3];  printf("%d\n",p1 > p2);  //0 printf("%d\n",p1 < p2);  //1 printf("%d\n",p1 == p2); //0 printf("%d\n",p1 != p2); //1

练习:

 int main() {      int arr[] = {10, 20, 30};     int *ptr;     ptr = arr;  //ptr指向arr首地址(第一个元素)     if (ptr == arr[0]) { //错误,类型不一样         printf("ok1\n");     }     if (ptr == &arr[0]) { // 可以         printf("ok2\n"); //输出     }     if (ptr == arr) { //可以         printf("ok3\n"); //输出     }     if (ptr >= &arr[1]) { //可以比较,但是返回false         printf("ok4\n");//不会输出     }      if (ptr < &arr[1]) { //可以比较,返回true         printf("ok5\n");//输出     }      return 0; }

【华南理工大学2018研】若有说明:int *p,m=5,n;,以下正确的程序段是(  )。 A.

p=&n;

scanf("%d",n);

B.

p=&n;

scanf("%d",*p);

C.

scanf("%d",&n);

p=n

D.

p=&n;

*p=n;

【答案】D

【解析】scanf语句中第二个参数应该是变量的地址,AB错误;C中p为指针变量,不可以直接把一个int型变量赋值给指针型,C错误;答案选D。

【华南理工大学2018研】若有定义:int *p,*s,c;,且各变量已正确赋值,则非法的赋值表达式是( )。 A.p=s B.c=*s C.*s=&p D.p=&c

【答案】C

【解析】C中p为指针变量,则&p表示的是指针的地址,若要赋值,则左边变量应该是一个二级指针,而*s代表的是s所指向地址的变量值,这个变量是一个int型,显然不正确。

【中央财经大学2018研】有如下说明

int a[10]={1,2,3,4,5,6,7,8,9,10}, *p=a;

则数值为9的表达式是(  )。 A.*p+9 B.*(p+8) C.*p += 9 D.p+8

【答案】B

【解析】A中*p=1,*p+9=10,A错误。C中*p得到的是1,加9后结果是10,C错误。D中p是地址,p+8仍然表示一个地址。因此B项正确,p+8指向元素9,进行取值得9。

3、野指针3.1 什么是野指针

野指针:就是指针指向的位置是不可知(随机性不正确没有明确限制的)。

3.2 野指针的成因① 指针使用前未初始化

指针变量在定义时如果未初始化,其值是随机的,此时操作指针就是去访问一个不确定的地址,所以结果是不可知的。此时p就为野指针。

 int main() {     int *p;     printf("%d\n",*p);      return 0; }

在没有给指针变量显式初始化的情况下,一系列的操作(包括修改指向内存的数据的值)也是错误的。

 #include<stdio.h> int main(){     int* p;                            *p = 10;            return 0; }

拓展:注意如下的赋值操作也是错误的

 int main() {     int num = 10;     int *p;     p = num;      return 0; }
② 指针越界访问
 #include <stdio.h>  int main() {     int arr[10] = {0};     int *p = arr;     for (int i = 0; i <= 10; i++,p++) {         *p = i;                   //i=10时越界     }     return 0; }

当i=10时,此时*p访问的内存空间不在数组有效范围内,此时*p就属于非法访问内存空间,p为野指针。

③ 指针指向已释放的空间

 #include <stdio.h>  int *test() {     int a = 10;     return &a;          //&a=0x0012ff40 }  int main() {     int *p = test();     printf("%d", *p);     return 0; }

调用test函数将返回值赋给p,test函数的返回值是局部变量a的地址。由于a只在test函数内有效,出了test函数其内存空间就被释放,也就意味着a的地址编号不存在,若将其赋值给p,导致p获取到的地址是无效的。

如果短时间内再次利用这块地址,它的值还未被改变也就是0x0012ff40还存在,p的值为0x0012ff40,*p时还是10,可以打印出。

但如果在打印之前有其他函数调用了这块地址,这块地址的名称就会发生变化,不再是0x0012ff40,打印*p时不再为10。

总之,此时p为野指针。

3.3 野指针的避免

1、指针初始化

定义指针的时候,如果没有确切的地址赋值,为指针变量赋一个 NULL 值是好的编程习惯。即

 int *p = NULL;

赋为 NULL 值的指针被称为空指针,NULL 指针是一个定义在标准库 <stdio.h>中的值为零的常量 #define NULL 0

后面如果用到指针的话再让指针指向具有实际意义的地址,然后通过指针的取值符号(*)改变其指向的内容。

练习:

 #include<stdio.h>  int main() {     int *p = NULL; //空指针不要与未初始化的指针混淆      int b = 8;     p = &b;   //显式赋值     *p = 100;     printf("%d\n", *p);  //100     printf("%d\b", b);   //100      return 0; }

2、小心指针越界

3、避免返回局部变量的地址

4、指针指向空间释放,及时置NULL

 int a = 10;          int* pa = &a; printf("%d\n", *pa);   pa = NULL;              //把pa指针置成NULL   printf("%d\n",pa);

5、指针使用之前检查有效性

 if (pa != NULL){     //进行使用 }  if (pa == NULL){     //不进行使用 }
4、二级指针(多重指针)

一个指针p1记录一个变量的地址。由于指针p1也是变量,自然也有地址,那么p1变量的地址可以用另一个指针p2来记录。则p2就称为二级指针

简单来说,二级指针即一个指针变量的值是另外一个指针变量的地址。通俗来说,二级指针就是指向指针的指针。

格式:

 数据类型 **指针名;

举例1:

 int a = 10; int *pa = &a;  //pa是一级指针 int **ppa = &pa; //ppa是二级指针,类型为int **

进而推理,会有int ***pppa = &ppa; 等情况,但这些情况一般不会遇到。

在上述代码基础上,

 int b = 20; ppa = &b; //报错

将 ppa(类型为 int **,即二级指针)赋值为 &b,但 &b是一个 int * 类型的指针,而不是 int ** 类型。这会导致类型不匹配的错误。

如果您想要将 ppa 指向 b,可以找一个额外的一级指针作为中介。如下操作:

 int b = 20; int *pb = &b;     // 使用一级指针来指向b ppa = &pb;        // 将ppa指向pb的地址,ppa是二级指针

举例2:

 int main() {     int var = 3000;     int *ptr = &var;        // 一级指针指向 var     int **pptr = &ptr;      // 二级指针指向 ptr     int ***ppptr = &pptr;   // 三级指针指向 pptr      printf("Value of var: %d\n", var);     printf("Value of ptr: %d\n", *ptr);         // 解引用一次     printf("Value of pptr: %d\n", **pptr);      // 解引用两次     printf("Value of ppptr: %d\n", ***ppptr);   // 解引用三次      return 0; }

举例3:使用malloc()函数创建二维数组

malloc()函数用于动态分配堆内存,free()函数用于释放堆内存。这两个函数通常都是配合一起使用的。

 #include <stdio.h> #include <stdlib.h>   int main() {     int rows, cols;// 定义二维数组的行和列     printf("第一维为:");     scanf("%d", &rows);     printf("第二维为:");     scanf("%d", &cols);      int **array = (int **) malloc(sizeof(int *) * rows);//先创建第一维     for (int i = 0; i < rows; i++) {         //在内层循环中动态创建第二维         array[i] = (int *) malloc(sizeof(int) * cols);         //         for (int j = 0; j < cols; j++) {             array[i][j] = 1;             printf("%d ", array[i][j]);         }         printf("\n");     }      free(array);      return 0; }

标签: #c语言中地址是多少位的 #c语言中地址是一串数字吗