指针和数组总结

本文详细介绍了C语言中的指针概念,包括指针的基本定义、类型、运算等,并对比了指针与数组的区别,还涉及了函数指针、指针数组及函数指针数组的应用。

指针

什么是指针

计算机内存中每个位置都有个独一无二的地址,每个地址上都存储着一个值,如果你记住了一个值的地址,就可以根据这个地址来访问地址上的值,但地址通常都用一串很长的号码来表示,这显然给我们的编程带来了诸多不便,因此我们应该给所知道的地址取一个温暖的名字,这些名字就被统称为指针变量。

指针类型

每个地址上存储的值的而类型都不尽相同,因此,为了操作的方便,指针也分为不同类型

int n;
char c;
int* pn = &n;   //将n的地址保存在int*类型的指针pn中
char* pc = &c;  //将c的地址保存在char*类型的指针pc中
//...

解引用指针

通过一个指针访问它所指向地址的内容,这个过程我们叫做解引用指针,用操作符*来完成。

int main()
{
    int n = 6;
    int* pn = &n;
    printf("*pn=%d",*pn);   //通过解引用pn,访问到n地址处的内容

    *pn = 233;      //通过指针改变n地址处的内容
    printf("n=%d",n); 
    return 0;
}

这里写图片描述

指针运算

  • 给指针加(减)1,指针向前(向后)移动的距离是由指针类型决定的。
    例如 :给char*类型的指针加1,指针将指向原来位置的下一个字节处;
    给int*类型的指针加1,指针将指向原来位置的下四个字节处。
  • 两个同类型指针相减,得到的值是以这个指针类型大小为单位分割的内存块的个数。

二级指针

二级指针即指针的指针,如果我们想访问指针的地址,就必须通过二级指针来访问。

    int n = 6;
    int *pn = &n;
    int **ppn = &pn;//ppn为二级指针

可以通过**ppn找到n的值

使用指针注意事项

  • 禁止使用未被初始化的指针。
    例如:
int* n;
*n = 6;

上面这段代码是不被允许的,因为我们没有对指针变量n进行初始化,我们无法预测n指向了那块内存,对里面的值进行访问和更改,是一件很危险的事情。所以,一定要对指针进行初始化。

  • 对指针赋值时一定要保证类型匹配,否则在操作指针是会访问不到原来的值。

  • 声明指针时不能连续声明多个,例如int* a,b;它表达的意思是声明了一个int*类型的a,和一个int类型的b。

  • 不能对NULL指针进行解引用。

数组

一维数组

数组表示一些相同类型的值的集合。例如,int a[5]表示一个数组里面有5个int类型的数据。其中a[0]表示第一个元素,a[4]表示最后一个元素。
数组名数字名是一个指针常量,它表示数组首元素的地址,它的类型取决于数组元素的类型。数组名放到sizeof内部时,返回的是整个数组的大小(20)而不是指针的大小。
那么,*(a+1)表示什么意思呢?
首先,a是一个指向整型的指针,所以加1加的是一个整型的长度,a表示的是第一个元素的地址,所以a+1表示的是第二个元素的地址,*(a+1)当然就是数组a的第二个元素了。对于这种表示方法,它比a[1]的效率更高 ,因为本质上编译器是把a[1]转化成*(a+1)来执行的。

二维数组

数组还可以用多维表示,我们以二维数组为例int a[3][4],我们可以认为这是一个三行四列的整型二维数组。但是,二维数组在内存的存储是连续的

这里写图片描述

那么,*(*(a+1)+2)表示什么意思呢?
在数组中,数组名表示数组首元素的地址,二维数组首元素其实是指第一行,所以给a加1跳过的是一整行,*(a+1)相当于的二行的数组名,给第二行的数组名加2表示第二行的第3个元素的地址,所以整个表达式式第二行第3个元素,相当于a[1][2]。

指针和数组

数组指针

当我们想让一个指针指向一个数组时,应该这样声明:int (*p)[5];
此时 ,p是一个数组指针,它指向一个数组,数组有5个元素,每个元素为int类型。

指针数组

当我们想让一个数组保存的类型为指针时,应该这样声明:int *p[5];
此时,p是一个数组,素组有5个元素,每个元素为int*类型。

指针和数组的区别

很多时候,我们可以把指针当成数组,把数组中当成指针。但是,指针和数组不是完全相同的,下面用指针和数组的定义和声明来说明问题。

  • 定义为数组,声明为指针
    这里写图片描述
    我们在文件1.c定义了一个数组a,我们试图以一个指针的形式声明数组a,然后输出数组的大小,下面是运行结果:
    这里写图片描述
    结果让人很意外,按道理整个数组的大小应该是20个字节,说明在2.c里编译器根本就没把a当成数组,而是当成一个指针,如果这里把a当成一个数组进行操作,显然是不合理的。

  • 定义为指针,声明为数组
    这里写图片描述
    同样的,我们在1.c里面定义一个字符串指针,然后在2.c里面声明成一个数组,我们天真的以为2.c的p里面保存的是”10086”的地址,然后很嗨皮的对它进行访问,结果如下:
    这里写图片描述
    结果又不尽人意,too naive!你以为你以为的就是正确的,可是编译器不这么认为,在2.c里面,编译器认为p是一个数组,它指向了另一块空间。

指针和数组的其它区别:

注:下面表格摘自《c专家编程》

指针数组
保存数据的地址保存数据
间接访问数据,首先取得指针内容,把它作为地址,
然后从这个地址提取数据。
如果指针有一个下标[i],就把指针的内容加上i作为地址
从中提取内容
直接访问数据
通常用于动态数据结构通常用于存储固定数目且数据类型相同的元素
相关的函数为malloc(),free()隐式分配和删除
通常指向匿名数据自身即为数据名

函数丶指针和数组

函数指针

可以把一个指针声明为指向函数的指针
例如: void (*pfun)();
pfun就是一个函数指针,它有能力指向一个函数,比如说我们有一个简单的函数

void print()
{
    printf("苟利国家生死以,岂因祸福避趋之。\n");
}

然后让pfun指向这个函数

pfun = print;//函数名就是函数的地址

我们可以通过函数指针来调用这个函数

int main()
{
    pfun();
    return 0;
}

这里写图片描述

其实 ,函数指针真正的用途是,当我们想给函数a里面传递另一个函数b时,我们可以通过传递一个指向函数b的函数指针作为函数a的参数来传递,这个过程被称作回调函数

函数指针数组

一个数组,数组元素了函数指针类型
例如void (*pfunarr[])();

函数指针数组指针

例如void (*(*pfunarr[]))();

它们三个的恩怨,如情丝,似乱麻,欲理,更乱。再写个十天十夜也就那样了,所以收工。

<think>我们收到用户关于指针数组复用的查询。根据用户提供的引用,我们了解到指针数组数组指针函数指针等相关概念。目标:解释指针数组复用的概念,并提供最佳实践。我们将从以下几个方面展开:1.理解指针数组的关系2.数组指针(指向数组指针)的复用3.指针数组(存放指针数组)的复用4.函数指针数组的复用5.动态内存分配下的复用6.最佳实践常见错误注意:用户引用中提供了数组指针函数指针的示例,我们将参考这些示例。由于引用[1]中提到了数组指针,引用[2]展示了返回数组指针函数,引用[3]提到了函数指针,引用[4]提到了函数指针在传递时的调用关系。步骤:1.首先说明指针数组在C/C++中的关系:数组名在大多数情况下可以视为指针,但并非所有情况(比如sizeof运算符下行为不同)。2.解释数组指针(指向数组指针指针数组(元素是指针数组)的区别,并说明复用方法。3.展示数组指针的用法,包括如何指向静态数组动态分配的多维数组。4.展示指针数组的用法,特别是字符串数组或动态指向不同数组的情况。5.函数指针数组的结合,例如函数指针数组(跳转表)用于复用不同的函数。6.动态分配内存时如何正确复用指针数组,避免内存泄漏。7.最佳实践:类型安全、使用智能指针(C++)、避免野指针、检查边界等。由于用户提到“复用”,我们重点放在如何通过指针数组的结合来复用代码数据结构。我们将结合引用中的例子,并给出自己的代码示例(注意:用户引用中[2]是一个C++的例子,我们以C++为例,但也会提到C)。注意:用户要求正确使用方法或解决相关问题,所以最佳实践部分要强调常见错误。开始回答:</think> ### 指针数组复用的方法与最佳实践 在C/C++编程中,指针数组的复用能提升代码效率灵活性,但也容易引发错误。以下是关键方法与实践建议: #### 1. **数组指针(指向数组指针)的复用** - **声明与初始化**: ```c int arr[3] = {1, 2, 3}; int (*ptr)[3] = &arr; // 指向整个数组指针 ``` 通过`(*ptr)[i]`访问元素(如`(*ptr)[0] = 1`)[^1]。 - **多维数组遍历**: 数组指针可直接遍历多维数组: ```c int matrix[2][3] = {{1,2,3}, {4,5,6}}; int (*p)[3] = matrix; // 指向第一行 for (int i=0; i<2; i++, p++) { for (int j=0; j<3; j++) printf("%d ", (*p)[j]); } // 输出:1 2 3 4 5 6 ``` 此方法避免多次解引用,提高效率[^1]。 - **动态分配**: 结合动态内存,实现运行时调整数组大小: ```c int (*dynArr)[4] = malloc(3 * sizeof(*dynArr)); // 分配3行4列数组 ``` **最佳实践**: - 用`typedef`简化复杂声明(如`typedef int ArrayPtr[4]; ArrayPtr *p;`)。 - 释放内存时确保匹配`free(dynArr)`[^1]。 --- #### 2. **指针数组(存放指针数组)的复用** - **声明与使用**: 存储多个指针,适合管理异构数据: ```c char *strArr[] = {"Hello", "World"}; // 字符串指针数组 int *intArr[3]; // 整型指针数组 ``` - **动态复用**: 灵活指向不同数据结构: ```c int a=1, b=2; int *ptrs[2] = {&a, &b}; *ptrs[0] = 10; // 修改a的值 ``` - **函数指针数组**: 实现策略模式或多态行为: ```c int add(int a, int b) { return a+b; } int sub(int a, int b) { return a-b; } int (*funcPtrs[2])(int, int) = {add, sub}; // 函数指针数组[^3] funcPtrs[0](5,3); // 调用add: 返回8 ``` **最佳实践**: - 为函数指针数组添加`NULL`哨兵值以便遍历结束。 - 避免悬空指针:动态分配的内存需在数组生命周期内有效[^3][^4]。 --- #### 3. **函数返回数组指针的复用** - **安全返回静态/动态数组**: 通过后置返回类型或`typedef`明确返回类型: ```cpp auto getArray(int size) -> int(*)[10] { // C++11后置返回类型[^2] static int arr[10]; return &arr; } ``` - **动态分配返回**: ```c int(*createMatrix(int rows))[4] { return malloc(rows * sizeof(int[4])); } ``` **最佳实践**: - 返回静态数组需注意线程安全问题。 - 动态分配的数组需在调用处显式释放。 --- #### 4. **常见错误与规避** 1. **数组指针混淆**: - 错误:`int *p = arr;`后使用`sizeof(p)`(返回指针大小,非数组大小)。 - 规避:用`sizeof(arr)/sizeof(arr[0])`计算数组长度[^1]。 2. **越界访问**: - 动态数组需记录边界(如`struct {int *data; size_t len;} dynArray;`)。 3. **内存泄漏**: - 为指针数组动态分配的元素需单独释放: ```c for (int i=0; ptrArray[i]; i++) free(ptrArray[i]); free(ptrArray); ``` --- ### 总结 - **复用场景**: - 数组指针 → 高效处理多维数据 - 指针数组 → 管理动态对象或函数集合 - 函数指针 → 实现回调与策略模式 - **核心原则**: 1. 明确区分数组指针 (`int(*p)[N]`) 指针数组 (`int *p[N]`)。 2. 动态内存分配需配对释放。 3. 使用`const`保护数据(如`const int(*p)[3]`)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值