C语言---09指针

本文详细介绍了C语言中的指针,包括指针的概念、定义、初始化、解引用、指针变量的类型和注意事项。讨论了通过指针引用数组和字符串的方法,如数组指针的偏移、数组与指针的关系以及字符串的指针操作。内容涵盖了指针的基础知识及其在数组和字符串操作中的高级应用。
摘要由CSDN通过智能技术生成

目录

一、指针的相关概念

(一)地址与指针

(二)指针变量

1、定义指针变量

2、初始化指针变量,避免指针变成野指针

3、解引用

4、指针所占用字节数

5、指针变量的类型(取值宽度、跨度)

 6、注意事项

二、通过指针引用数组、字符串

(一)指向数组元素的指针

1、指针指向的偏移

2、指针本身的偏移

3、数组的[]和*()的关系(重要)

4、arr和&arr的区别(了解)

 5、指向同一数组中两个元素的指针变量之间的关系

 6、实例

(二)通过指针引用字符串


一、指针的相关概念

(一)地址与指针

  • 一个变量的地址称为该变量的指针

  • 地址常量:变量和数组的地址是系统分配的,在程序运行过程中不可被改变

 int a;
 &a = 666; // 不能用一个整数给一个指针变量赋值
 scanf("%d", &a); // 取址符&:取变量的地址
 // 需要知道变量的地址才能往对应的地址中放入数据
 printf("%p\n", &a); // %p是地址格式占位符(十六进制地址)
 char str[10];
 printf("%p\n", str); // 数组名代表数组的首地址

(二)指针变量

  • 本质就是一个变量

  • 只不过是C语言中专门用于存储地址的一类型变量

1、定义指针变量

  • 数据类型 *指针变量名(*修饰指针变量名)

  • 指针变量名:需要符合标识符的命名规则

  • 数据类型:指针变量定义时的数据类型称为这个指针的“基类型” ,指针变量的基类型就决定着它能够存储什么类型的地址

 int a = 123;
 int *p; // 定义了一个基类型为int类型的指针变量,名为p  
 p = &a; // 指针变量可以用于存储地址,地址是个常量不能修改
 p = a; // 这是错误的,指针变量不能存储变量的值,也不能存储实际数据

2、初始化指针变量,避免指针变成野指针

  • 没有初始化指针变量,指针的指向是随机地址编号,指针变成野指针

  • 指针需要有指向之后才能够引用地址中的值,如果没有合法的空间,可以初始化为*p=NULL

3、解引用

  • 引用指针所指向地址中的值

  • *p表示取p所保存的地址对应的空间内容

 int a = 123,*p = &a;
 *p = 987; // *p:指针的“解引用”
 printf("a=%d\n", a); // 987 可以通过指针间接改变变量的值
 printf("*p=%d\n", *p); // 987
  • &取地址符和*指针解引用符区别

    • num的类型是 int 类型,&num的类型是 int * 类型

    • p的类型是 int *类型 ,*p的类型是 int 类型

  • 高级总结:如果&和*同时存在可以相互抵消(从右‐‐>左)

  • 论证:*p == num ,*p = *&num == num,&*&*&num == &num

int num = 10;
int *p;
p = #

printf("p=%p\n",p); // 0019FED8
printf("&*&**&p=%p\n",&*&**&p); // 0019FED8

4、指针所占用字节数

  • 和其基类型无关

  • 一般的指针类型所占字节数为4字节,因为指针变量只是用于存储地址而已,一个地址占用8个十六进制位,也就是32个二进制位,也就是4个字节

 printf("&a=%d\n", &a); // 以十进制的形式输出a的地址
 printf("p=%d\n", p); // p中存储的是a的地址&a
 printf("&p=%d\n", &p); // 指针p也有自己的存储空间:指针的地址
 // 指针变量也是变量,也会占用存储空间
 printf("sizeof(a)=%d\n", sizeof(a)); // 4字节
 // 指针变量占用的内存大小是多少?
 printf("sizeof(p)=%d\n", sizeof(p)); // 4字节?4
 char *q;
 double *r;
 printf("sizeof(q)=%d\n", sizeof(q)); // 1字节?4
 printf("sizeof(r)=%d\n", sizeof(r)); // 8字节?4

5、指针变量的类型(取值宽度、跨度)

  • 对于int *p;

    • 指针变量自身类型:只将指针变量名拖黑剩下啥类型指针变量自身就是啥类型。 p自身的类型是int *

    • 指针变量所指向的类型:将指针变量名和离它最近的*一起拖黑剩下啥类型就指向啥类型。p所指向的类型是int 

  • 例如int ***p;自身类型为int ***,所指向的类型为int **

  • 指针变量的取值宽度,即取多少字节的内容出来

  • 宽度:由指针变量所指向类型长度决定

  • 计算机是小端存储,按照04030201顺序存储,按照01020304顺序读取

  • p指向int类型,宽度为4字节,输出0x1020304

int num = 0x01020304;
int *p;
short *p2;
char *p3;

p=#
p2=#
p3=#

printf("*p = %#x\n", *p);
printf("*p2 = %#x\n", *p2);
printf("*p3 = %#x\n", *p3);

  • 指针变量的跨度,即将指针从首地址移动多少字节

  • 跨度:由指针变量所指向类型长度决定

  • +1指的是跨过相应类型的字节数

int num = 0x01020304;
int *p;
short *p2;
char *p3;

p=#
p2=#
p3=#

printf("p=%u\n",p); 
printf("p+1=%u\n",p+1); // 跨度是四个字节

printf("p2=%u\n",p2);
printf("p2+1=%u\n",p2+1); 

printf("p3=%u\n",p3);
printf("p3+1=%u\n",p3+1); 

  • 应用1:自定义指针变量p,int num = 0x1020304,取出0x0102

  • 思路:将指针移动两个字节,然后读取两个字节宽度

int num = 0x01020304; // 存储格式为04030201
short *p = #

printf("%#x\n",*(p+1)); // 0x102

  • 应用2:自定义指针变量p,int num = 0x1020304,取出0x0203

  • 思路:将指针移动1个字节,然后读取两个字节宽度

  • 宽度与跨度不等时需要进行强制类型转换,选择min(宽度、跨度)定义指针变量

int num = 0x01020304; // 存储格式为04030201
char *p = #
short *p2 = #


printf("%#x\n", *(short *)(p+1)); // 0x203
printf("%#x\n", *(short *)((char *)p2+1)); // 临时改为1字节,跨一字节,0x203

 6、注意事项

  • void不能定义变量,void *可以定义变量p,但是对于p不能直接使用*p操作

void num; // 不知道num大小
void *p; // void *指针类型,32位平台系统知道指针类型的大小为4字节

// 对于p不能直接使用*p操作,需要对其进行强制类型转换
int num = 10;
void *p = #
printf("*p = %d\n", *p); // 错误,因为p的指向类型为void,系统确定不了宽度
printf("*p = %d\n", *(int *)p); // p临时的指向类型为int,系统确定宽度4B
  • 定义一个指针变量p但没有初始化,p指向了一个未知空间,系统不允许取值*p操作

  • 指针指向NULL,NULL是(void *)0地址,是内存的起始地址,受系统保护,不允许取值*p操作

  • 不要给指针变量赋普通的数值

  • 指针变量不要操作越界的空间,即类型需要匹配

char num=10;
int *p = #
// num只占1B空间 而p的指向类型为int 所以*p取值宽度为4B,所以越界3B

二、通过指针引用数组、字符串

(一)指向数组元素的指针

 int *p;
 p = arr; // 数组的数组名代表数组的首地址
 p = &arr[0]; // 使指针p指向数组的首地址(数组第一个元素的地址)    
  • 指针带下标的形式访问数组元素

 printf("%d\t", p[i]); // []自带解引用
  • 指针指向偏移的形式访问数组元素

 printf("%d\t", *(p + i)); 

1、指针指向的偏移

  • 指针的指向向后或向前移动几个基类型的内存

  • 如:p+1就是从p指针指向的当前位置向后偏移一个基类型的内存

  • 指针本身的位置并没有变化

2、指针本身的偏移

  • 很少用指针本身的偏移

  • 指针本身向前或向后移动多个基类型的内存

  • 以指针本身的偏移访问整个数组后,不能再继续偏移输出,因为指针移动的位数大于数组的大小,最后输出乱值

 arr[]={0,1,2,3,4,5,6,7,8,9};
 p = &arr[2];
 printf("%d\n", *p); // 输出2
 p++; // 3
 p + 1; 
 printf("%d\n", *p); // 2 并没有改变指针p的值

3、数组的[]和*()的关系(重要)

  • 本质:[ ]是*( )的缩写

  • 缩写规则:+左边的值 放在[ ]左边,+右边的值 放在[ ]里面

  • 例如:arr[1] == *(arr+1) == *(1+arr) == 1[arr]

  • arr代表的是第0个元素的地址(&arr[0])&arr[0] == &*(arr+0) == arr+0 == arr

// 数组名arr 作为类型 代表的是数组的总大小 sizeof(arr)
// 数组名arr 作为地址 代表的是首元素地址(第0个元素的地址)
int arr[5]={10,20,30,40,50};
int n = sizeof(arr)/sizeof(arr[0]);
int *p = NULL;

// p = &arr[0];
// arr == &arr[0]
p = arr;

4、arr和&arr的区别(了解)

  • arr:数组的首元素地址,arr+1:跳过一个元素

  • &arr:数组的首地址,&arr+1:跳过整个数组

  • arr与&arr虽然地址编号一样,但是类型完全不一样

  • 数组名arr是一个地址常量,不能被赋值

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

printf("arr = %u\n",arr);
printf("arr+1 = %u\n",arr+1);

printf("‐‐‐‐‐‐‐‐‐‐‐‐‐\n");

printf("&arr = %u\n",&arr);
printf("&arr+1 = %u\n",&arr+1);

 5、指向同一数组中两个元素的指针变量之间的关系

  • 指向同一数组的两个指针变量

  • 相减,返回的是相差元素的个数

  • 可以比较大小 > < >= <= == !=

  • 可以相互赋值

  • 尽量不要相加,避免越界

  • 指针变量里面在不越界的情况下可以为负数

    • int *p2 = arr+3;

    • printf("%d\n",p2[‐2]);

    • p2[-2]=*(p2-2)=*(arr+3-2)=*(arr+1)=arr[1]=20

p2可以往左移动
void test11()
{
    int arr[5]={10,20,30,40,50};
    int *p1 = arr; // 10
    int *p2 = arr+3; // 40

    printf("%d\n",p2‐p1); // p2=p1+3 p2-p1=3

    if(p2>p1)
    {
        printf(">\n");
    }
    else
    {
        printf("<=\n");
    }

    p1 = p2; // p1和 p2指向同一处

    printf("p2=%u\n",p2);
    // p1+1是移动一个数组元素空间,p1+p2是移动p2大小倍的数组元素空间,越界了

    printf("%d\n",p2[‐2]); // p2[-2]=*(p2-2)=*(arr+3-2)=*(arr+1)=arr[1]=20
}

 6、实例

  • ++、*同一优先级,结合性从右至左

int arr[5]={10,20,30,40,50};
int *p = arr;

printf("%d\n", *p++); // 10
printf("%d\n", (*p)++); // 20
printf("%d\n", *(p++)); // 21
  •  *p++:因为后置++的特性(先使用后加减),所以这里p没有移动,输出10,然后指针右移

  • (*p)++:*p是20,输出20,然后令(*p)=(*p)+1,数组中第二个元素20被改为21

  • *(p++):p先与*结合使用(小括号没起作用),输出21,然后再指针右移

(二)通过指针引用字符串

 char *q = "abcedfg";
 puts(q); // 字符串输出
 char str[10] = "123456";
 q = str;
 puts(q);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盾山狂热粉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值