指针全解(系统全面整理)

本文详细介绍了指针的概念、类型、内存大小,以及基础操作如解引用、避免野指针和指针运算。还涵盖了const修饰指针、字符指针、数组与指针的配合、函数指针等内容,帮助读者掌握C/C++中的重要指针技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一.指针定义

1.概念

2.指针变量及类型

3.指针内存大小

二.指针基础操作

1.解引用

2.避免野指针

2.1野指针成因

2.2预防野指针

2.3 assert断言

3.指针运算

3.1指针 + - 整数

3.2指针 - 指针

4.const修饰指针

4.1 const在*左边

4.2 const在*右边

三.指针进阶用法

1.字符指针变量

2.数组与指针搭配

2.1数组名的理解

2.2指针访问数组

2.3指针数组

2.4数组指针

3.函数指针


一.指针定义

1.概念

如果把计算机内存看作一座楼房,那么地址就是门牌号,就像根据门牌号找到房间,同样根据地址能找到内存空间,地址便是指针。

2.指针变量及类型

存放地址的变量叫做指针变量,我们平时口中所说的指针通常也是指的指针变量。我们使用取地址操作符(&)拿到地址并存放在指针变量中,指针变量也具有不同的类型。

//例如此处对int类型变量a取地址,存放在int*类型的指针变量中
//可以这样理解指针类型:*表明p是指针变量,存放指针,int表面该指针指向对象为int类型
int a = 1;
int* p = &a;

//以下是常见指针类型,详细在后文介绍
void* p1;//指向对象未知类型,常用于函数
char* p2;//指向对象是char类型
int* p3;//指向对象是int类型
int** p4;//指向对象是int*类型
int(*p5)[5];//指向对象是int [5]类型
int* (*p6)[10];//指向对象是int* [10]类型

3.指针内存大小

指针大小取决于环境,32位环境(X86)下地址是32bit位(4字节),64位环境(X64)下地址是64bit位(8字节),并且值得注意的是指针变量的大小与类型无关,无论指向对象是什么类型,地址的大小在相同环境下都是相同的。

二.指针基础操作

1.解引用

如何使用指针变量?使用解引用操作符*对指针变量进行解引用便能得到指针指向的内容。 

int a = 1;
int* p = &a;
*p = 10;//解引用p后便能得到a,可以进行赋值等运算

2.避免野指针

2.1野指针成因

//指针未初始化
int* p;
*p = 10;

//指针越界访问
char arr[5] = {0};
char* p = &arr[0];
for(int i = 0 ; i<=6 ; i++)
{
    *(p+i) = i;
}  

//指针指向的空间释放
int* test()
{
    int a = 1;
    int* p = &a;
    return p;
}

2.2预防野指针

针对以上三种出现野指针的问题,最好是记住范围不要越界访问,也不要返回函数内局部变量的地址,初始化的问题可以先用NULL对指针进行初始化,要使用时再对指针进行检查,如果为NULL则停止程序进行即可,避免危险使用。

2.3 assert断言

以上要实现对NULL指针的检查,可以使用assert断言进行,使用时需要头文件assert.h,它的好处有很多,书写简便,能准确返回出错行数信息,并且在确保程序完好运行后只需要在 #include<assert.h>前定义一个宏NDEBUG,编译器就能自动禁用程序里所有的assert断言,再次想使用时去掉宏就行了,十分方便调试。

#define NDEBUG
#include<assert.h>

assert(p!=NULL);

3.指针运算

3.1指针 + - 整数

指针加减可以理解为指针向前或向后移动,指针+1可以看作指针向前走一步,而走一步有多大(距离)取决于指针的类型。

int a = 1;
char b = 'a';
int* p1 = &a;
char* p2 = &b;
printf("%p\n", p1);
printf("%p\n", p1+1);//指针是int* 类型,一步就是4个字节,地址+4
printf("%p\n", p2);
printf("%p\n", p2+1);//指针是char* 类型,一步就是1个字节,地址+1

 

3.2指针 - 指针

为什么没有指针+指针的运算?可以把两个指针看作两个日期,日期-日期得到相差的天数,同理指针-指针可以得到之间的元素个数(前提是两个指针指向同一片空间),那么日期+日期是什么呢?没有意义,故也不存在指针+指针的用法。

//例如自己实现strlen函数
int my_strlen(char* p)
{
    char * p = s;
    while(*P != '\0')
        p++;
    return p - s;//两指针相减得到字符串的字符个数
}

4.const修饰指针

4.1 const在*左边

以下两种情况属于同一种类型——const在*左边。可以这样理解:const修饰*p,*p是解引用后的值,意味着*p无法修改。即该情况修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变,但是指针变量本⾝的内容可变。

//例如:
int a = 1;
int b = 2;
const int *p = &a;//两种写法等效
int cosnt *p = &a;

*p = 3;//错误的
p = &b;//正确的

4.2 const在*右边

与以上情况相反,const在*右边,修饰p本身,p这个指针变量内装的地址无法改变。即该情况修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

//例如:
int a = 1;
int b = 2;
int* const p = &a;

*p = 3;//正确的
p = &b;//错误的

三.指针进阶用法

1.字符指针变量

字符指针有个特殊点,使用字符指针可以储存一个字符串的地址(字符串首元素的地址),这一点与字符数组区别开。若两个字符指针指向同一个常量字符串,那么它们的地址是相同的。

//p1的地址等于p2的地址
const char* p1 = "fresh hacker is so handsome!";
const char* p2 = "fresh hacker is so handsome!";

//p3的地址却不等于p4的地址
char p3[] = {"fresh hacker is so handsome!"};
char p4[] = {"fresh hacker is so handsome!"};

2.数组与指针搭配

2.1数组名的理解

一般来说,数组名都指的是数组首元素的地址。但有两个例外。第一个是sizeof(arr1)的时候,这时得到的是整个数组的大小,这种情况只能是sizeof(数组名),如sizeof(arr1+0)中arr1就代表数组首元素地址,结果就是地址的大小了。第二个就是&arr2的时候,这时候得到的整个数组的地址,需要使用数组指针储存。

2.2指针访问数组

在此需理解一个概念,arr[1]实际本质上就是*(arr+1),数组实际就是指针。因此在函数传参时需要传入数组时,通常使用指针传参。

void test(arr[10],int sz)//arr本质是指针,所以通常写成int *arr
{
    for(int i = 0; i < sz; i++)
    {
        printf("%d ",*(arr+i));//这里换成arr[i]也是一样的
    }
}

int arr[10] = {0};
int sz = sizeof(arr)/sizeof(arr[0]);

2.3指针数组

指针数组也是数组,不过数组里的每个元素是指针而已,写法为:int* arr[10];唯一与平常不同的是int换成了int*,也就是数组内每个元素的类型由int 变为了int*(指针)类型。可以使用指针数组来模拟二维数组。

//指针数组模拟二维数组
int arr1[3]={1,2,3};
int arr2[3]={2,3,4};
int arr3[3]={3,4,5};

int* parr[3] = {arr1,arr2,arr3};//指针数组,此处数组名表示数组首元素地址

for(int i = 0; i < 3; i++)
{
    for(int j = 0; j < 3; j++)
    {
        printf("%d ",parr[i][j]);//parr[0]得到arr1,再arr1[j]访问元素
    }
}

2.4数组指针

数组指针也是指针,不过指向的是数组。写法要与指针数组区分开,写法为:int (*p) [10];由于[]的优先级要⾼于*号的,所以必须加上()来保证p先和*结合,p与*结合便能表示这是个指针变量。int(*p)[10]表示该指针指向一个拥有10个元素的整形数组。取出数组的地址使用&数组名。

二维数组可以看作每个元素是一维数组的数组,因此根据数组名是首元素地址,二维数组的数组名可以看作一个一维数组的地址,这就需要使用数组指针存储该地址。

//二维数组传参的本质就是数组指针
int arr[2][3] = {{1,2,3},{4,5,6}};
test(arr,2,3);

void test(int(*p)[3] , int row ,int col)//传int p[2][3]是数组形式,但本质上是指针
{
    for(int i = 0; i<row ; i++)
    {
        for(int j = 0; j<col ; j++)
        {
            printf("%d ",*(*(p+i)+j));
        }
        printf("\n");
    }
    
}

3.函数指针

函数指针同样是指针,不够指针指向的对象是函数。没错,函数同样具有地址,函数名就是函数的地址。此处假设定义一个int test(int x,int y)的函数,则函数指针写法为:int (*p)(int , int);同样的*和变量名p要用括号括起来,这才能表达这是一个指针,而(*p)前面是指向函数的返回类型,后面是指向函数的参数类型。

int test(int x,int y)
{
    return x + y;
}

int (*p)(int,int) = &test;
int (*p)(int,int) = test;//两种写法皆可

int ret = (*p)(2,3);
int ret = p(2,3);//使用时两种写法皆可

printf("%d\n",ret);

当然函数指针也可以和数组组合,函数指针数组就是应用之一。书写方法为:int (*p[4])(int ,int);根据[]的优先级要⾼于*号,这样的写法自然便是数组形式,这就是函数指针数组。如果要把几个函数的地址存在一个数组,前提是每个函数类型相同,即函数的参数和返回类型相同。

int Add(int x, int y)
{
    return x + y;
}

int Sub(int x, int y)
{
    return x - y;
}

int Mul(int x, int y)
{
    return x * y;
}

//以上三个函数类型相同,可以存在一个函数指针数组中
int (*p[3])(int,int) = {Add,Sub,Mul};

int ret = p[0](1,2);//p[0]得到Add,再加上参数就能使用函数了
printf("%d\n", ret);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值