复制这段内容打开「百度网盘APP 即可获取」
https://pan.baidu.com/s/1cP2NZUhntvbWGLl1hRU66g
一、数组与指针
一、定义
指针:C语言中某种数据类型的数据存储的内存地址,例如:指向各种整型的指针或者指向某个结构体的指针。
数组:若干个相同C语言数据类型的元素在连续内存中储存的一种形态。
数组在编译时就已经被确定下来,而指针直到运行时才能被真正的确定到底指向何方。所以数组的这些身份(内存)一旦确定下来就不能轻易的改变了,它们(内存)会伴随数组一生;
而指针则有很多的选择,在其一生他可以选择不同的生活方式,比如一个字符指针可以指向单个字符同时也可代表多个字符等。
指针和数组在C语言中使用频率是很高的,在极个别情况下,数组和指针是“通用的”,比如数组名表示这个数组第一个数据的指针。
如下代码
#include <stdio.h>
char array[4] = {1, 2, 3, 4};
int main(void)
{
char * p;
int i = 0;
p = array;
for (; i < 4; i++)
{
printf("*array = %d\n", *p++);
}
return (0);
}
这里我们将数组名array作为数组第一个数据的指针赋值给p。但是不能写成*array++。准确来说数组名可以作为右值,不能作为左值(左值和右值的概念这里不再展开讲解)。
数组名的值其实是一个指针常量,这样我想你就明白了数组名为什么不能做为左值了。如果想用指针p访问array的下面2的数据,以下写法是合法的
char data;
/*第一种写法*/
p = array;
data = p[2];
/*第二种写法*/
p = array;
data = *(p+2);
/*第三种写法*/
p = array + 2;
data = *p;
二、指针与二维数组
先说一下二维数组,二维数组在概念上是二维的,有行和列,但在内存中所有的数组元素都是连续排列的,它们之间没有“缝隙”。以下面的二维数组 a 为例:
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
从概念上理解,a 的分布像一个矩阵:
0 1 2 3
4 5 6 7
8 9 10 11
但是内存是连续的,没有这样的“矩阵内存”,所以二维数组a分布是连续的一块内存。
先说一下二维数组,二维数组在概念上是二维的,有行和列,但在内存中所有的数组元素都是连续排列的,它们之间没有“缝隙”。以下面的二维数组 a 为例:
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
从概念上理解,a 的分布像一个矩阵:
0 1 2 3
4 5 6 7
8 9 10 11
但是内存是连续的,没有这样的“矩阵内存”,所以二维数组a分布是连续的一块内存。
C语言允许把一个二维数组分解成多个一维数组来处理。对于数组 a,它可以分解成三个一维数组,即 a[0]、a[1]、a[2]。每一个一维数组又包含了 4 个元素,例如 a[0] 包含 a[0][0]、a[0][1]、a[0][2]、a[0][3]。那么定义如下指针如何理解呢?
int (*p)[4];
表明 p 是一个指针,它指向一个数组,数组的类型为int [4],这正是 a 所包含的每个一维数组的类型。
那么和下面定义有什么区别呢?
int *p[4];
和[]的优先级了,[]的优先级是高于*的,所以int *p[4];等同于int *(p[4]);。所以它是一个指针数组。
这里很绕,总接下
int (*p)[4];是数组指针,它指向二维数组中每个一维数组的类型,它指向的是一个数组。
int *p[4];是指针数组,它是一个数组,数组中每个数是指向int型的指针。
三、 指针数组与数组指针
#include <stdio.h>
int main()
{
int a[3][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}};
int(*p)[4];
p = a;
printf("%d\n", sizeof(*(p + 1)));
return (0);
}
对于数组指针p如下
那么printf(“%d\n”, sizeof(*(p + 1)));的结果就是16。
如果想打印a[1][0]的值,代码如下
printf("%d\n", *(*(p + 1)));
如果想打印a[1][1]的值,代码如下
printf("%d\n", *(*(p + 1)+1));
这个自行体会,p是数组指针,它指向的是一个数组,所以对获取它指向的值,也就是*p,是指向一个数组还是一个值,指向a[0]。获取获取a[0][0],就需要写成**p。
对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的数据类型有关,p 指向的数据类型是int [4],那么p+1就前进 4×4 = 16 个字节,p-1就后退 16 个字节,这正好是数组 a 所包含的每个一维数组的长度。也就是说,p+1会使得指针指向二维数组的下一行,p-1会使得指针指向数组的上一行。
最后再次捋一下数组指针和指针数组,
int *p1[4];是指针数组。
int (*p2)[4];是数组指针。
“[]”的优先级比“*”要高。
对于指针数组,p1先和“[]”结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素。那么它本质是一个数组,这个数组里有4个指向int类型数据的指针。
对于数组指针,“()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那么它本质是一个指针,它指向一个包含4个int 类型数据的数组。
既然深入谈了指针数组和数组指针,就多聊一下。
#include <stdio.h>
int main()
{
char a[5] = {'A', 'B', 'C', 'D'};
char(*p3)[5] = &a;
char(*p4)[5] = a;
return 0;
}
上面代码是编译编译是报了waring的,报警如下
注意:不同的编译器编译结果可能不同,我的编译方法请参考《使用vscode编译C语言》。
p3 这个定义的“=”号两边的数据类型完全一致,而p4 这个定义的“=”号两边的数据类型就不一致了。左边的类型是指向整个数组的指针,右边的数据类型是指向单个字符的指针。所以才有了上面的警告。
但由于&a 和a 的值一样,而变量作为右值时编译器只是取变量的值,所以运行并没有什么问题。不过编译器仍然警告你别这么用。
再举一个栗子
int vector[10];
int matrix[3][10];
int *vp,*vm;
vp = vector;
vm = matrix;
上面的代码第5行是错误的,因为vm是指向整型的指针,但是matrix不是指向正向的指针,他是指向整型数组的指针。下面是正确的写法
int matrix[3][10];
int (*vm)[10];
vm = matrix;
四、数组指针的应用
上面说了那么多,可能大部分开发者用不到,数组指针在很多时候都是可以代替二维数组的,有些程序员喜欢用指针数组来代替多维数组,一个常见的用法就是处理字符串。
#include <stdio.h>
char *Names[] =
{
"Bill",
"Sam",
"Jim",
"Paul",
"Charles",
0};
void main()
{
char **nm = Names;
while (*nm != 0)
printf("%s \n", *nm++);
}
具体运行我就不讲解了,运行结果如下
注意数组中的最后一个元素被初始化为0,while循环以次来判断是否到了数组末尾。具有零值的指针常常被用做循环数组的终止符。
这种零值的指针称为为空指针(NULL)。采用空指针作为终止符,在增删元素时就不必改动遍历数组的代码,因为此时数组仍然以空指针作为结束。
五、特殊操作
写到这里想到一个“特殊操作”,先看下面代码是否正确。
p[-1]=0;
初看这句代码,觉得奇怪,甚至觉得它就是错误,日常C语言开发基本有见到小标是负数的,但是仔细想想没有哪一本书说过下标能为负数的。看下面代码
void main()
{
int data[4] = {0, 1, 2, 3};
int *p;
p = data +2;
printf("p[-1] is %d\n",p[-1]);
printf("*(p-1) is %d\n",*(p-1));
}
运行结果如下
不仅可以编译通过,还能正确的输出结果为1。这表明,C的下标引用和间接访问表达式是一样的。当然不鼓励这种骚操作,代码需要很强的可读性,而不是这样的骚操作,这里只是演示下标引用和简介表达式的关系。
六、分别写出BOOL,int,float,指针类型的变量a与零的比较语句。
BOOL : if ( !a ) or if(a)
int : if ( a == 0)
float : const EXPRESSION EXP = 0.000001
if ( a < EXP && a >-EXP)
pointer : if ( a != NULL) or if(a == NULL)
七、简述数组与指针的区别?
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可
以随时指向任意类型的内存块。
(1)修改内容上的差别
char a[] = “hello”;
a[0] = 'X';
char *p = "world"; // 注意p 指向常量字符串
p[0] = 'X'; // 编译器不能发现该错误,运行时错误
(2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof§,p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12 字节
cout<< sizeof(p) << endl; // 4 字节
计算数组和指针的内存容量
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4 字节而不是100 字节
}
九、C++求数组元素个数
已知一个数组a,求其中的元素个数:
我们只需把问题转换为求数组元素a[0]占用空间的大小上,a[0]占用的空间
大小很好确定,即sizeof(a[0]),这就是sizeof(int)的等价条件,所有通过数
组名确定元素个数的表达式如下:
int l = sizeof(a)/sizeof(a[0]);
十、int (*s[10])(int) 表示的是什么?
int (*s[10])(int) 函数指针数组,每个指针指向一个int func(int param)的函
数。
十一、数组和指针的区别与联系
一、概念
数组:数组是用于储存多个相同类型数据的集合。
指针:指针相当于一个变量,但是它和不同变量不一样,它存放的是其它变量在内存中的地址。
2.存储方式
数组:数组在内存中是连续存放的,开辟一块连续的内存空间。数组是根据数组的下进行访问的,多维数组在内存中是按照一维数组存储的,只是在逻辑上是多维的。数组的存储空间,不是在静态区就是在栈上。
指针:指针很灵活,它可以指向任意类型的数据。指针的类型说明了它所指向地址空间的内存。
指针:由于指针本身就是一个变量,再加上它所存放的也是变量,所以指针的存储空间不能确定。
3.求sizeof
数组:
数组所占存储空间的内存:sizeof(数组名)
数组的大小:sizeof(数组名)/sizeof(数据类型)
指针:
在32位平台下,无论指针的类型是什么,sizeof(指针名)都是4,在64位平台下,无论指针的类型是什么,sizeof(指针名)都是8。
数组传参时,会退化为指针,
(1)退化的意义:C++语言只会以值拷贝的方式传递参数,参数传递时,如果只拷贝整个数组,效率会大大降低,并且在参数位于栈上,太大的数组拷贝将会导致栈溢出
(2)因此,C++语言将数组的传参进行了退化。将整个数组拷贝一份传入函数时,将数组名看做常量指针,传数组首元素的地址。
十二、数组指针和指针数组的区别
指针数组本质上是数组,每个数组元素指向一个int型变量的地址,数组占多少个字节由数组本身决定。
数组指针本质上是指针,是一个指向一个数组的指针变量,32位系统下永远占4个字节。
数组指针(也称行指针)
定义 int (*p)[n]😭)优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
指针数组
定义 int * p[n];
[]优先级高,先与p结合成为一个数组,再由**int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 p=a; 这里p表示指针数组第一个元素的值,a的首地址的值。
数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存
在内存当中,占有多个指针的存储空间。还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。比如要表示数组中i行j列一个元素:
*(p[i]+j)**、*(*(p+i)+j)**、**(*(p+i))[j]、p[i][j]
十三、函数参数传值、传引用和传指针的区别分别是什么?
指针是一种数据类型,指针和其他整形、浮点型等变量没有什么区别,只不过其他变量比如整型变量,它的变量数值一般是个整数,比如1,2等;但
是指针的变量数值是一个地址而已。
传值:将实参传给形参,赋值完后实参和形参没有任何联系了,因此形参的
改变对实参没有影响。
传地址:是把实参的地址传给形参,这也形参的改变就会影响到实参。因为形参和实参对象指向的地址是一样的,所以因此形参指向的对象就是实参的
对象。传地址本质也是值的传递,传的是地址,值传递特点是被调函数对形式参数的任何操作都是作为局部变量来进行的,不会影响到主调函数的实参
的值,但是因为传的是地址,因此其实也在改变了这个地址上的变量。形参和实参实际是相互独立的。因为是传地址,如果对传进来的指针进行++操
作,则不再能改变实参的值,所以因此很多传递指针的时候会用const进行修饰。int* const n
传引用:没有任何拷贝,就是两个变量直接指向同一个对象,形参相当于是实参的林外一个别名,因此形参改变,实参也改变。传指针穿的是一个变量
值,本质是值传递,而传引用,传进来直接是一个地址,因此形参被当作间接寻址。所以对形参的操作都影响实参。但是实际我认为本质就是传指针有
可能可以对传进来的指针变量的地址改变了,则形参不再影响到实参,但是传引用则直接只能对实参进行作用
十四、引用与指针有什么区别?
➢ 相同点:
●都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。
➢ 不同点:
●指针是一个新的变量(实体),指向另一个变量的地址,我们可以通过访问这个地址来修改另一个变量;而引用是一个别名,对引用的操作就是对
变量的本身进行操作
●引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;
●引用没有 const(指的是int &const),指针有 const(指的是int
*const,const 的指针不可变;
●引用不能为空,指针可以为空;
●“sizeof 引用”得到的是所指向的内存的大小,而“sizeof 指针”得到的是指
针本身的大小;
●指针和引用的自增(++)运算意义不一样;
●引用是类型安全的,而指针不是 (引用比指针多了类型检查)
●指针可以有多级,引用只有一级
●指针的大小一般是4个字节(32位系统上是4个字节,而16位系统上2字
节),引用的大小取决于被引用对象的大小
●传参的时候,使用指针的话需要解引用才能对参数进行修改,而使用引
用可以直接对参数进行修改
-
引用必须被初始化,指针不必。
-
引用初始化以后不能被改变,指针可以改变所指的对象。(此处说的“引用初始化以后不能被改变”不是指变量的值不可变,而是指向的变量地址不可变。)
-
不存在指向空值的引用,但是存在指向空值的指针。
在编译器看来, int a = 10; int &b = a; 等价于 int const b = &a; 而 b =20; 等价于 *b = 20; 自动转换为指针和自动解引用。
十五、在函数参数传递的时候,什么时候使用指针,什么时候使用引用?
- 需要返回函数内局部变量的内存的时候用指针。使用指针传参需要开辟内存,用完要记得释放指针,不然会内存泄漏。而返回局部变量的引用是没有意义的
- 对栈空间大小比较敏感(比如递归)的时候使用引用。使用引用传递不需要创建临时变量,开销要更小
- 类对象作为参数传递的时候使用引用,这是C++类对象传递的标准方式
可以改变所指的对象。(此处说的“引用初始化以后不能被改变”不是指变量的值不可变,而是指向的变量地址不可变。)
- 不存在指向空值的引用,但是存在指向空值的指针。
在编译器看来, int a = 10; int &b = a; 等价于 int const b = &a; 而 b =20; 等价于 *b = 20; 自动转换为指针和自动解引用。
十五、在函数参数传递的时候,什么时候使用指针,什么时候使用引用?
- 需要返回函数内局部变量的内存的时候用指针。使用指针传参需要开辟内存,用完要记得释放指针,不然会内存泄漏。而返回局部变量的引用是没有意义的
- 对栈空间大小比较敏感(比如递归)的时候使用引用。使用引用传递不需要创建临时变量,开销要更小
- 类对象作为参数传递的时候使用引用,这是C++类对象传递的标准方式
[外链图片转存中…(img-HIw5E6b6-1704403103835)]
注意的是形参传进来的数组会退化成指针所以用指针,因为指针传进来也是值拷贝,所以说只是*p=a,把p传入函数中变成 *q=a