C复习-数组与指针的区别+指针数组

news/2025/3/5 12:14:33/

参考: 里科《C和指针》


一维数组

int b[10];

在C中,在几乎所有使用数组名的表达式中,b是一个指针常量,即数组第一个元素的地址,其类型是数组元素的类型,所以b是一个指向int的常量指针。

但是数组和指针是完全不同的:数组具有确定数量的元素,而指针只是一个标量值。只有当数组名在表达式中使用时,编译器才会为它产生一个指针常量。只有两种情况下,数组名不用指针常量表示:当数组名作为sizeof或单目&的操作数时。前者返回整个数组的长度,后者返回的是指向数组的指针。

int a[10];
int *c;
c = &a[0];
// 等价于
c = a;
// 因为&a[0]是指向数组第一个元素的指针,也就是数组名的值

索引时用到逗号会如何?不报错,但是只取最后一个值

int b[] = {1,2,3};
// 输出3,因为逗号会只保留最后一个值,即b[2]
cout << b[1, 2] << endl;

array[sub]与 *( array + (sub) )的含义是相同的

int array[10];
int *ap = array + 2;
// 此时ap是指向array[2]的指针ap[0] // 等价于*(ap+(0)),所以答案是array[2],是值
*ap + 6 // 等价于array[2] + 6
*(ap + 6) // 等价于array[8]
ap[-1] // 等价于array[2-1] = array[1]
2[array] // = *(2 + array) = array[2].编译器可以理解,但是人不行,不要写

假设指针和下标都正确使用,那么下标绝对不会比指针更有效率,而指针有时候比下标更有效率。

int array[10], a;
for ( a = 0; a < 10; a += 1 )array[a] = 0;int array[10], *ap;
for ( ap = array; ap < array + 10; ap++ )*ap = 0;

上面的程序涉及指针的移动。对于下标法来说,因为array是指向array[0]的指针,每次取下标的时候都要将a与sizeof(int)相乘;但是对于指针法,在for语句上,每次需要移动sizeof(int),但这个值是在编译时计算出来的,循环时不用每次都计算,所以会更有效率一点。

a = get_value();
// 下面这两种的效率就一样,因为a是不确定的,都需要经过乘法计算
array[a] = 0;
*( array + a ) = 0;

不要为了效率上的细微差别而牺牲可读性。如果需要在自己的环境里取得最高效率,可能要看机器版本(看机器指令是否紧凑)

  1. 当以固定数目的增量在数组中移动时,使用指针变量比下标效率高,尤其上面那种for循环
  2. 声明为寄存器变量的指针通常比位于静态内存和堆栈中的指针效率高(具体看机器
  3. 如果可以通过通过内容判断循环结束,则不需要单独的计数器
  4. 那些必须在运行时求值的表达式,比&array[size]或array+size这样的常量表达式代价高
#define SIZE 50
int x[SIZE];
int y[SIZE];
int i;
int *p1, *p2;// 初始版本
void
try1()
{for(i = 0; i < SIZE; i++)x[i] = y[i];
}//指针版本
void
try2()
{for(p1 = x, p2 = y; p1 - x < SIZE;)*p1++ = *p2++
}// 最终,最快
void
try5()
{register int *p1, *p2;for(p1 = x, p2 = y; p1 < &x[SIZE]; )*p1++ = *p2++;
}

这些改动在某些追求峰值效率的时刻比较重要,比如某些时候需要对即时发生的事件做出最快的反应。另外,对于那些占了绝大部分运行事件的代码,也需要着重改进。

一维数组作为函数参数

如果将数组名作为参数传递给一个函数,那么实际传递的是一个指针的拷贝,这个指针是指向数组第一个元素的指针。就是说,可以改变其指向位置的值,但是指向本身不能改。

只要有可能,函数的指针形参都应该声明为const

/*
** 把第2个字符串复制到第1个中
** string使用const是因为:a.方便阅读原型即知哪些参数不可修改 b.可以捕获试图修改的错误
** c.指允许向函数传递const参数
*/
void
strcpy1( char *buffer, char const *string) 
{while ((*buffer++ = *string++ ) != '\0' );
}int main()
{char c[10] = {'a', 'b'};char d[10];char* s = c;char* b = d;strcpy1(b, s);for (int i = 0; i < 2; i++)cout << b[i] << endl;return 0;
}

虽然形参也可以写成 char buffer[],编译器也能接受,但是写成指针形式是更准确的。这也是为什么写函数原型时,一维数组形参不用写明元素数目,因为形参实际是指针,指向的是已经在其他地方分配好内存的空间。

字符串数组初始化

前者是初始化一个字符数组的元素,跟用列表初始化一致;后者是初始化一个指针,指向的是存储一个字符串常量“hello”的位置。

char msg[] = "hello";
char *msg2 = "hello";

多维数组

多维数组可以理解成切块,比如int a[3][4][5] 可以理解成先分3块,每个里面4小块,小块里面再分5块,然后每个最小的块是一个元素。用列表初始化的时候也这么写,就大括号嵌套。

int matrix[3][10];

matrix + 1指向的是第二行;*(matrix + 1)是指向第二行第一个元素的指针

*(matrix + 1) + 5 也是指针,只是指向的位置是matrix[1][5]

*( *(matrix + 1) + 5 ) 如果作为右值使用,就是取得存储在matrix[1][5]的值,如果作为左值使用,这个位置将被存一个新值

指向数组的指针

int matrix[3][10];
int (*p)[10] = matrix; // 它的移动是逐行的,p指向matrix第0行,移动到下一行的时候+10
// int *mp = matrix 是错误的,因为matrix是指向整型数组的指针,而mp是指向整型的指针
// int (*p)[] = matrix; 如果打算在指针上执行运算,就不能这样声明。
// 因为此时数组长度未知,那么移动到下一行要去哪里?编译器可能移动0,也可能报错// 如果需要一个指针逐个访问整型元素,使用下面两种方法
int *pi = &matrix[0][0]; 
int *pi = matrix[0]; 

多维数组作为函数参数

void func2( int (*mat)[10] );
void func2( int mat[][10] );void fun2( int **mat );

前两种是对的,因为编译器必须知道第2个及以后各维的长度才能对下标进行求值。第1维的长度不需要,除非要判断是否越界才行,但是计算下标值用不到。最后一个不对,因为mat指一个指向整型指针的指针,并不是指向整型数组的指针。

多维数组的初始化

int mat[2][3] = { 100, 101, 102, 110, 111, 112 };
int mat[2][3] = {{ 100, 101, 102 };{ 110, 111, 112 }
};

使用花括号初始化的好处是可以省略值,比如下面相当于给four_dim[0][0][0][0]赋值100,给four_dim[1][0][0][0]赋值200,其他是0。如果是使用列表初始化,那么100和200之间要填充0,一来繁琐二来易错。

int four_dim[2][2][3][5] = {{{{100}}},{{{200}}}
}

另外,使用花括号初始化时可以不写第1维,因为编译器可以推断出来。但是剩下的维度必须写明,因为如果需要编译器推测的话,必须要有一个完整写出的子初始值。那还是写明维度比较好,这样所有的初始值列表都可以不完整。

指针数组

先假定这是一个表达式。下标引用的优先级高于间接访问,所以api是某种类型的数组(长度是10),在取得一个数组元素后,执行间接访问,那么它的结果是一个整型值。所以api是一个数组,它的元素类型是指向整型的指针。

int *api[10];

下面第一种是创建了一个指针数组,每个指针元素都初始化指向不同的字符串常量,指针数组本身要占据空间,但是每个字符串常量占据的空间比矩阵小;第二种是创建了一个矩阵,每行是一个数组,存单词(含NUL),不需要指针。如果需要存储的字符串长度差不多,那么用矩阵ok,但如果差别很大,那用指针数组好一些,取决于指针占用的空间是否小于固定长度存储浪费的空间。

char const keyword[] = {"do","for",
};char const keyword[][9] = {"do","for",
};

但除非是非常大的表,二者的区别很小,因此不重要。常用的方法如下,末尾加一个NULL指针可以在搜索时检测表的结尾,而无需预先知道表的长度。

char const *keyword[] = {"do","for",NULL
};
// 使用NULL的好处:
for( kwp = keyword; *kwp != NULL; kwp++ )

http://www.ppmy.cn/news/1194469.html

相关文章

ubuntu 分区 方案

ubuntu 分区 方案 自动分区啥样子的&#xff1f; 手动分区 需要怎么操作&#xff1f; 注意点是啥&#xff1f; swap分区 要和 内存大小 差不多 安装ubuntu系统时硬盘分区方案 硬盘分区概述 一块硬盘最多可以分4个主分区&#xff0c;主分区之外的成为扩展分区。硬盘可以没有…

速拿offer,超全自动化测试面试题+答案汇总,背完还怕拿不到offer?

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、你会封装自动化…

Java面向对象 下(六)

Java面向对象 ( 下) 观看b站尚硅谷视频做的笔记 文章目录 Java面向对象 ( 下)1、 关键字&#xff1a;static1.1、static 的使用1.1.1、static 修饰属性1.1.2、 static 修饰方法1.1.3、 static 修饰代码块1.1.4、 static 修饰内部类1.1.5、类变量 vs 实例变量内存解析 1.2、 自…

独家分享 | BI零售数据分析模板,可视化,更易懂

“人、货、场”是零售数据分析的三大关键&#xff0c;只要能又快又透彻地掌握这三大关键的数据情况&#xff0c;即可为零售运营决策提供关键的数据支持&#xff0c;提高盈利、降低成本、优化采购库存结构等。奥威BI软件这套BI零售数据分析模板套装围绕“人、货、场”预设了数十…

explain详解

explain详解 explainexplain extended&#xff08;5.7以前的版本&#xff09;expain partitions&#xff08;5.7以前的版本&#xff09;show warnings&#xff1a;经过mysql优化的语句。 explain结果说明 id&#xff1a;执行顺序&#xff0c;id越大执行优先级越大 select_ty…

Linux下的IMX6ULL——构建bootloader、内核、文件系统(四)

前言&#xff1a; Linux 平台上有许多开源的嵌入式linux系统构建框架(框架的意思就是工 具)&#xff0c;这些框架极大的方便了开发者进行嵌入式系统的定制化构建&#xff0c;目前比较常 见的有OpenWrt, Buildroot, Yocto,等等。其中Buildroot功能强大&#xff0c;使用 简单&…

图的广度优先遍历的单源路径、无权图的最短路径问题、BFS性质附Java代码

目录 使用BFS求解单源路径问题 BFS重要性质 无权图的最短路径问题 使用BFS求解单源路径问题 import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.Queue;public class SingleSourcePath {private Graph G;private i…

QML WebEngineView 调用 JavaScript

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 在 QML 与 Web 混合开发时,除了使用 WebEngineView 加载网页之外,我们还可以在 QML 层运行 JavaScript 代码,这样就能更灵活地操作浏览器窗口和网页内容,从而实现丰富的交互功能了。例如:获取网页标题、…