7.指针

一、变量与地址

指针(pointer)是一个值为地址的变量(或者数据对象)。
引入指针:

  • 正如char类型变量的值为字符、int类型变量的值为整数、指针类型的变量是地址。
    type name = value ==> int *p = &n

p是一个存储int类型变量地址的指针,即指向int类型变量的指针。

  • type: int *
  • name: p
  • value: &n
  • & and * 分别表示取址运算符、间接运算符。*对于定义指针变量时用于声明,后跟地址时用于解引用,给出存储在指针指向的地址上的值。*之后有无空格不影响输出,一般习惯上声明时,加上空格:char * p,解引用时不加空格:a = *p

取址符 &

  • c语言中的变量是存放在内存中的,因此每一个变量都会被分配给一个地址。而&可以取出该变量的地址。便且只能取变量与数组的地址(存储在内存中的对象),不能取表达式的地址。
  • 在不同位数的编译器中,int 类型所占字节大小与地址所占的字节数不同。
  • 变量在内存中的存储是连续进行存放的,并且先定义的变量位于内存的上方空间?
  • 在初始时对变量类型进行定义的原因是:变量的类型决定了变量所占用的内存空间,因此定义变量类型就是为了给该变量分配合适的存储空间,以便存放数据。

示例代码:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 
  4 int main()
  5 {
  6     int i = 0;
  7     int j = 1;
  8 
  9     printf("i --> %p\n",&i);
 10     printf("j --> %p\n",&j);
 11 
 12     exit(0);
 13 
 14 }

输出结果:

jxs@jxs-ubuntu:~/Desktop/c  language/pointer$ ./add_get 
i --> 0x7fff47989710
j --> 0x7fff47989714

二、指针与指针变量

  • 指针是用于存储变量地址的一个变量。

将内存比作为一家酒店,内存的单位长度是字节byte(8bit),酒店用面积进行划分。不同数据类型的变量占用不同大小的字节数,每一个房间占用酒店的面积与之类似,为标记每个房间具体在哪,使用房间号来标记,要想知道每一个变量位于内存空间中的哪个地方,则地址就是标记存储变量的那块空间的首地址。而指针就是存储这块空间的地址的一组存储单元(通常是两个或四个字节)。

使用格式:

int i;
int *p = &i;  //先对i取址,再将取到的地址以 int 类型保存在 p 中,*p = i的值。

TYPE (int *) NAME (p) = VALUE (&i)

示意图:

int *p,q;  //定义指针p,int 类型变量q,
int* p,q;  //同上
//若要同时定义两个指针:
int *p,*q;

指针变量

普通变量的值是实际的值,指针变量的值是有实际值的变量的地址。

int *p p只会存储别的变量(这里是指整数)的地址。

示例代码:

//表示指针变量与变量之间的关系
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 
  4 int main()
  5 {
  6     int i = 1;
  7     int *p = &i;  //这里定义的指针类型要与指向的变量的数据类型相同
  8 
  9     float *j,*k;
 10     
 11     printf("size of i :%lu\n",sizeof(i));
 12     printf("size of p :%lu\n",sizeof(p));
 13     printf("size of j :%lu\n",sizeof(j));
 14     printf("size of k :%lu\n",sizeof(k));
 15         
 16     printf("i = %d\n",i);
 17     printf("&i = %p\n",&i);
 18     printf("p = %p\n",p);
 19     printf("&p = %p\n ",&p);
 20     
 21     
 22     
 23     exit(0);
 24 }

输出结果:

jxs@jxs-ubuntu:~/Desktop/c  language/pointer$ ./pointer
size of i :4
size of p :8
size of j :8
size of k :8  //指向不同数据类型的指针所占的内存相同
i = 1
&i = 0x7fff272a30ec
p = 0x7fff272a30ec   //p存储i的地址
&p = 0x7fff272a30f0  //可见指针p也是一个变量,存储变量值的地址的一个变量
  • 多级指针
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 
  4 int main()
  5 {
  6     int i = 1;
  7     int *p = &i;  //A level pointer
  8     int **q = &p;  //Secondary pointer  二级指针调用
  9 
 10     printf("i = %d\n",i);
 11     printf("&i = %p\n",&i);
 12     printf("\n");
 13     
 14     printf("p = %p\n",p);
 15     printf("&p = %p\n ",&p);
 16     printf("\n");
 17     
 18     printf("q = %p\n",q);
 19     printf("&q = %p\n ",&q);
 20    // printf("*q = %ld\n",*q);
 21     printf("*q = %p\n",*q);
 22     printf("**q = %d\n",**q);
 23     printf("\n");
 24     
 25     
 26     
 27     exit(0);
 28 }

输出结果:

xs@jxs-ubuntu:~/Desktop/c  language/pointer$ ./dua_p
i = 1
&i = 0x7ffe94766984

p = 0x7ffe94766984
&p = 0x7ffe94766988
 
q = 0x7ffe94766988
&q = 0x7ffe94766990
 *q = 0x7ffe94766984  // = p = &i (去p的地址把p的值取出来)
**q = 1  // = *(&i) = i = 1 (去i的地址把i的值取出来)

三、直接访问与间接访问

四、空指针与野指针

空指针是地址为0的那块空间,将指针置为空指针的目的在于防止野指针。

这块指针是无法进行访问的,即对其进行解引用操作是不允许的。

int *p = NULL;

野指针就是指指向的的变量的地址不明确,指向一个非法的或已销毁的内存的指针。指针随意指向一个地址时,即使可以执行,但是属于非法访问,会提示段错误。

指针的使用要么指向合法的地址,要么将其置为空指针,避免野指针出现。

五、空类型指针

void *p

万能指针,能接受任意类型的指针,可以指向任何类型的对象。因此不能对空类型指针进行解引用操作。必须强制类型转换成相应的指针类型,才能进行解引用操作。

空类型指针不能直接进行解引用操作,且不能进行整数运算。

六、定义与书写规则

七、指针运算

取址、解引用、关系运算。

示例代码:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 
  4 int main()
  5 {   
  6     int a[] = {15,25,15,1,5,9};
  7     int *p = &a[3];  // *p = 1
  8     int y;
  9     
 10     y = (*--p)++;  //先进性p自减,则由值1所在的地址转向值1所在的地址,在进行解引用获得该值
 11     printf("y = %d\n",y);  // 自增在后因此y = 15
 12     printf("a[2] = %d\n",a[2]);
 13  
 14     exit(0);
 15 }

输出结果:

jxs@jxs-ubuntu:~/Desktop/c  language/pointer$ ./pointer_cacu 
y = 15
a[2] = 16
y = 15

八、指针与数组

1.指针与一维数组

数组是地址常量,而指针是存储地址的变量。除此区别外,两者可以互换使用。++数组表示法其实就是在变相地使用指针++。
在观察两者的区别之前先了解一下数组本身的特点:
数组名就是这个数组在内存中的首地址

//定义数组arr[], 则有;
arr == &arr[0];  //数组第一个元素的地址值等于数组名也就是整个数组在内存中的起始地址值,两者都是常量,但可以将值赋给指针变量。

所以就有:

a[i]: a[i] = *[a + i] = *[p + i] = p[i];
&a[i]: &a[i] = [a + i] = [p + i] = &p[i];
  • 同样的:在定义函数时,int arr[]int *arr都表示arr是一个指向int 类型的一个指针。int arr只能用于声明形参。而int *arr指向的不仅仅是一个int类型的值,而且还是指向一个整形的数组。
  • 函数定义:int sum(int *arr, int n)int sum(int arr[], int n)两者等价。

示例代码:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 
  4 int main()
  5 {
  6     int a[3] = {1,2,3};
  7     int *p = a;  //no '&' before a, it's a array 
  8     int i;
  9 
 10     for(i = 0; i < sizeof(a)/sizeof(a[0]);i++)
 11     {
 12         printf("a[%d]:%p --> %d\n",i,&a[i],a[i]);
 13         printf("a + %d :%p --> %d\n",i,a + i,a[i]);
 14         printf("p + %d :%p --> %d\n\n",i,p + i,a[i]);
 15     }   
 16     
 17     
 18     exit(0);
 19 }

输出结果:

jxs@jxs-ubuntu:~/Desktop/c  language/pointer$ ./pointer_1d
a[0]:0x7ffc9ef5a1fc --> 1
a + 0 :0x7ffc9ef5a1fc --> 1
p + 0 :0x7ffc9ef5a1fc --> 1

a[1]:0x7ffc9ef5a200 --> 2
a + 1 :0x7ffc9ef5a200 --> 2
p + 1 :0x7ffc9ef5a200 --> 2

a[2]:0x7ffc9ef5a204 --> 3
a + 2 :0x7ffc9ef5a204 --> 3
p + 2 :0x7ffc9ef5a204 --> 3

另一种书写方式:

 18     int i;
 19     int *p = (int [3] ){1,2,3};
 20 
 21     printf("size of p = %ld\n",sizeof(p));
 22     
 23     for(i = 0; i < 3;i++) 
 24         printf("%p --> %d\n",&p[i],p[i]);

输出结果:

jxs@jxs-ubuntu:~/Desktop/c  language/pointer$ ./pointer_1d
size of p = 8  //不是12的原因是在指定编译系统中指针所占的内存是相同的,64位操作系统占用8个字节
0x7ffc6c38821c --> 1
0x7ffc6c388220 --> 2
0x7ffc6c388224 --> 3

2.指针与二维数组

示例代码:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 
  4 #define M 2
  5 #define N 3
  6 
  7 int main()
  8 {
  9     int a[M][N] = {1,5,8,6,98,54};
 10     int i,j;
 11 
 12     printf("a --> %p\na + 1 -->  %p\n",a,a + 1);
 13     printf("\n");
 14     for(i = 0;i < M;i++)
 15     { 
 16         for(j = 0;j < N;j++)
 17         {   
 18             printf("%p --> %d\n",&a[i][j],a[i][j]);
 19         }
 20         printf("\n");
 21     }
 22     
 23     exit(0);
 24 }   

输出结果:

jxs@jxs-ubuntu:~/Desktop/c  language/pointer$ ./pointer_2d 
a --> 0x7ffccde14e20
a + 1 -->  0x7ffccde14e2c  //指向值6所在的地址

0x7ffccde14e20 --> 1
0x7ffccde14e24 --> 5
0x7ffccde14e28 --> 8

0x7ffccde14e2c --> 6
0x7ffccde14e30 --> 98
0x7ffccde14e34 --> 54

其中将程序改写成下面会得到同样的结果:

printf("%p --> %d\n",*(a + i) + j,*(*(a + i) + j));


可知:
&a[i][j] = *(a + i) + j
a[i][j] = *(*(a + i) + j)

*a:由于a是二级指针,a + i表示矩阵的第i行首地址。因此取*给其降级,降级一级指针,就类似于一维数组,但是仍然表示地址常量,**a表示二维数组首地址存储的值。

    printf("a --> %p\na + 1 -->  %p\n",a,a + 1);
    printf("\n"); 
    printf("a --> %d\na + 1 -->  %d\n",**a,**(a + 1));

输出:

/*
    a  -->  1  5  8
a + 1  -->  6  98 54
*/
a --> 0x7ffc91d663a0   //二维数组第一行首地址
a + 1 -->  0x7ffc91d663ac  二维数组第二行首地址

a --> 1  // *a 表示一个一级指针,可以在列上移动,**a 表示一级指针地址所存储的值
a + 1 -->  6

另一只中输出格式:

 11     int *p = &a[0][0];
 12     
 13     for(i = 0;i < 6;i++)
 14     {
 15         printf("%d ",p[i]);
 16     }
 17     printf("\n");

输出结果:

jxs@jxs-ubuntu:~/Desktop/c  language/pointer$ ./pointer_2d
1 5 8 6 98 54 

3.指针与字符数组

使用格式:char *str = "hello"
示例代码:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 
  5 int main()
  6 {
  7 
  8     char str1[] = "hello";  //这里定义的str1[]为存储字符串的变量,其值可以被覆盖
  9     strcpy(str1,"world");
 10     puts(str1);
 11     
 12     char *str = "hello";  //出错的原因在于指针str指向的地址中存储的字符串为常量,常量不可修改
 13     strcpy(str,"world");  
 14     puts(str);
 15     
 16        
 17     exit(0);
 18 }

输出结果:

jxs@jxs-ubuntu:~/Desktop/c  language/pointer$ ./pointer_char 
world
Segmentation fault (core dumped)  //段错误

九.const 与指针

区别于define宏的使用,定义常量进而实现一改全改,但其不进行语法检查。实现一改全改的另外一种方式是用变量来存储常量值,但是变量的值会发生改变,因此为防止该种情况出现,使用const对变量进行约束,使其一直为原来的常量值。

//常用的使用格式:
const int a;
int const a;

const int *p;  //常量指针
int const *p;

int *const p;  //指针常量

const int *const p;  //既是指针常量又是常量指针

示例代码:

  7     const int pi = 3.14;
  8     pi = 3.1415926;
  9     printf("%d\n",pi);

输出结果:

const.c:8:8: error: assignment of read-only variable ‘pi’
    8 |     pi = 3.1415926;
      |        ^
make: *** [<builtin>: const] Error 1

在使用指针时,可以实现对变量的值进行间接修改,但是这是危险的。

  • 指针常量与常量指针:
    整型变量要用整形指针来指向,整型常量要用常量化指针来指向

1.常量指针 (int const *p)--> 本质是指针,指针指向的值不发生变化

指针的指向可以发生变化,但是当前指针所指向的空间中的变量值不能发生变化。
示例代码:

 15     int i = 10;
 16     int j = 20;
 17     
 18     int const *p = &i;
 19     *p = 100;
 20     printf("%d\n",*p);
 //output:
 const.c:19:8: error: assignment of read-only location ‘*p’  //说明指向的变量值不能改变
   19 |     *p = 100;  
      |        ^
  若19行修改为 p = &j;  //说明指向的地址可以改变
  //output:
  20  //j的值

2.指针常量 (int *const p)--> 本质是常量,指针指向的空间不发生变化

指针的指向不可以发生变化,但是当前指针所指向的空间中的变量值可以发生变化。
示例反上。

3.const int *const p

指针的指向不可以发生变化,当前指针所指向的空间中的变量值也不可以发生变化。
示例代码:

//对上述代码中,
const int *const p = &i;
//follow are false! The pointed address and pointed value can't change
p = &j;
*p = 15;

十、指针数组与数组指针

1.指针数组(本质是一个数组)

使用格式:【存储类型】 数据类型 * 数组名 [长度]
示例:int * arr[3]:TYPE:int *[3]

char *arr[5] = {"A","D","C","B","E"};

示例代码:

//实现字母正序排列:使用选择排序法

2.数组指针(本质是一个指针)

表示在数组的指定空间大小的移动
使用格式:【存储类型】 数据类型 (*指针名) [下标] = 值
示例:int (*p)[3] = [VALUE]:TYPE: int[3] *p

int *p 表示的是一个指向存储整形数据的指针,移一位增加一个int类型大小,指向下一个地址。
对于a[2][3],int (*p)[3] 则表示p每移动一位,就会增加 3 个 int 类型大小,指向下一行的首地址。 


示例代码:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 
  4 #define M 2
  5 #define N 3
  6 
  7 int main()
  8 {
  9     int a[M][N] = {1,5,8,6,98,54};
 10     int i,j;
 11     
 12     int (*q)[3] = a;
 13     printf("a --> %p\na + 1 -->  %p\n",a,a + 1);
 14     printf("q --> %p\nq + 1 -->  %p\n",q,q + 1);
 15     printf("\n"); 
 16     exit(0);
 17 }

输出结果:

jxs@jxs-ubuntu:~/Desktop/c  language/pointer$ ./pointer_2d
a --> 0x7ffefe850710
a + 1 -->  0x7ffefe85071c
q --> 0x7ffefe850710
q + 1 -->  0x7ffefe85071c

此时数组指针qa可以互换,唯一的区别就是q位变量,p为常量。

十一、多级指针

posted @ 2023-06-29 17:06  假行僧me  阅读(6)  评论(0编辑  收藏  举报