C语言重学系列之指针与数组

文章详细阐述了C语言中数组的初始化方式,包括部分初始化和全初始化,以及数组与指针的关系。指针的使用,特别是const关键字在指针声明中的作用,强调了const指针不能修改所指向的值。还讨论了二维数组、数组名的特性以及函数指针的声明和使用。此外,提到了零长度数组和变长数组的概念,前者用于结构体的扩展,后者在函数参数中允许动态大小的数组。
摘要由CSDN通过智能技术生成

数组

使用花括号括起来的一系列数值来初始化数组。数值之间用逗号隔开,在数值和逗号之间可以使用空格符。

如果不初始化数组,数组值是随机值;如果部分初始化数组,未初始化的元素则被设置为0。

当使用空的方括号对数组进行初始化时,编译器会根据列表中的数值数目来确定数值大小。

在初始化列表中使用带有方括号的元素下标可以指定某个特定的元素。
int arr[6] = {[5] = 212};
如果 int days[12] = {31, 28, [4] = 31, 30, 31, [1]=29};
那么days[12]={31, 29, 0,0, 31, 30, 31, 0, 0, 0, 0, 0};

C不支持把数组作为一个整体来赋值,也不支持用花括号括起来的列表形式进行赋值(初始化的时候除外)。

float rain[5][12];
rain[0]是包含12个元素的数组,rain[0][0]是一个float数。
二维数组初始化的时候可以省略内部的花括号,只保留最外面的一对花括号。
int sq[2][3]={{5, 6}, {7, 8}};
int sq[2][3] = {5, 6, 7, 8};


来源: 《C Primer Plus》第五版,加上自己的理解

const

int sum(const int ar[], int n);
这样使用const并不要求原始数组时固定不变的,这只是说明函数在处理数组时,应把数组当成是固定不变的。
将常量或非常量数据的地址赋给指向常量的指针是合法的。只有非常量数据的地址才可以赋给普通的指针。

来源: 《C Primer Plus》第五版

const int val[5] = {1,2,3,4,5};

数组val的值不能被修改。需要在声明const数组时对其进行初始化。

int val[5] = {1,2,3,4,5};
const int *p = val;

p是指向const int的指针,不能修改p指向的数组的值。

*p = 3;   错误 
p[0] = 3; 错误
val[0] = 3; 正确
int * const p = val;
p = &val[1];非法,不可以修改p指向的地址

int *p1;
const int *p2;
const int **pp2;
p1 = p2;非法,把const指针赋给非const指针
p2 = p1;合法,把非const指针赋给const指针
pp2 = &p1; 非法,把非const指针赋给const指针
把非const指针赋给const指针是允许的,这样的赋值有一个前提,只进行一层间接运算
在进行两层间接运算时,这样的赋值不再安全。如果允许这样赋值,可能会产生如下问题:
const int **pp2;
int *p1;
const int n = 13;
pp2 = &p1; 不允许,但我们假设允许
*pp2 = &n; 合法,两者都是const。但这同时会使p1指向n
*p1 = 10; 合法,但这将改变const n的值

来源: 《C Primer Plus》第五版

数组名同时是该数组首元素的地址。
在C中,对一个指针加1的结果是对该指针增加1个存储单元。对数组而言,地址会增加到下一个元素的地址,而不是下一个字节。
1.指针的数值就是它指向的对象的地址。
2.在指针前运用运算符就可以得到该指针所指向的对象的数值。
3.对指针加1,等价于对指针的值加上它指向的对象的字节大小。
C语言标准在描述数组时,借助了指针的概念。例如,定义ar[n]时,意思是
(ar + n),即“寻址到内存中的ar,然后移动n个单位,再取出数值”。

在函数原型或函数定义头的场合中(并且也只有在这两种场合中),可以用int *ar代替int ar[]:Int sum(int ar[], int n);
无论在任何情况下,形式int *ar都表示ar是指向int的指针。形式int ar[]也可以表示ar是指向int的指针,但只是在声明形式参量时才可以这样用。

由于原型允许省略名称,下面的4种原型是等价的:
int sum(int *ar, int n);
int sum(int , int);
int sum(int ar[], int n);
int sum(int [], int);

C保证在为数组分配存储空间的时候,指向数组之后的第一个位置的指针也是合法的。
start++ 一元运算符和++具有相等的优先级,但它在结合时是从右向左进行的。这就意味着++应用于start,而不是应用于
start。

将一个整数加给指针:这个整数都会和指针所指类型的字节数相乘,然后所得的结果会加到初始地址上。
从指针中减去一个整数:这个整数都会和智者所指类型的字节数相乘,然后所得的结果会从初始地址中减掉
两个指针间的差值:差值的单位是相应类型的大小。


来源: 《C Primer Plus》第五版

数组名

在这里插入图片描述在这里插入图片描述
&num和num的地址相同,但是&num的类型是数组指针,num的类型是整型指针。
&num + 1相当于num+5
num++;//非法,数组名不能自增,因为数组名是不能修改的左值。

int zippo[4][2];

因为zippo是数组首元素的地址,所以zippo的值和&zippo[0]的值相同;zippo是两个整数大小对象的地址。

zippo[0]是包含2个整数的数组,zippo[0]的值同其首元素和&zippo[0][0];zippo[0]是一个整数大小对象的地址。

因为整数和两个整数组成的数组开始于同一个地址,因此zippo和zippo[0]具有相同的数值。但是zippo+1和zippo[0]+1的结果不同。
zippo代表zippo[0]的值,但是zippo[0]本身就是一个int数的地址,即&zippo[0][0],因此zippo是&zippo[0][0];
*(zippo[0])代表存储在zippo[0][0]中的数值,即一个Int数值。

指向二维数组的指针变量:
int (*p) [2];
为什么使用圆括号?因为表达式中[]的优先级高 *。
int *pax[2]; 两个指针构成的数组

int sum2(int ar[][], int rows);错误声明
因为首方括号表示这是一个指针,而其他方括号描述的是所指向对象的数据类型。

来源: 《C Primer Plus》第五版

int main(void)
{
	int zippo[4][2] = {{2,4}, {6,8}, {1,3}, {5,7}};
	int (*pz)[2];
	pz = zippo;
	printf("pz = %p, pz + 1 = %p\n", pz, pz + 1);
	printf("pz[0] = %p pz[0] + 1 = %p\n", pz[0], pz[0] + 1);
	printf("*pz = %p, *pz + 1 = %p\n", *pz, *pz + 1);
	printf("pz[0][0] = %d\n", pz[0][0]);
	printf("*pz[0] = %d\n", *pz[0]);
	printf("**pz = %d\n", **pz);
	printf("pz[2][1] = %d\n", pz[2][1]);
	printf("*(*(pz+2)+1)=%d\n", *(*(pz+2)+1));
	return 0;
}

输出结果:
pz = 0x0064fd38 pz + 1 = 0x0064fd40
pz[0] = 0x0064fd38 pz[0] + 1 = 0x0064fd3c
*pz = 0x0064fd38 *pz + 1 = 0x0064fd3c
pz[0][0] = 2
*pz[0] = 2
**pz = 2
pz[2][1] = 3
*( *(pz +2)+ 1) = 3

来源: 《C Primer Plus》第五版

int main()
{
    static char *s[] = {"black", "white", "pink", "violet"};
    char **ptr[] = {s+3, s+2, s+1, s}, ***p;
    p = ptr;
    ++p;
    printf("%s", **p+1);
    return 0;
}
答案:ink
题目来源:牛客网

s是一个数组,数组元素是指针。
static char *s[]可以转换成static char **s。
s = &s[0],s指向的对象类型是字符串,s+1就是指向下一个字符串
ptr是一个数组,数组元素是二级指针。
char **ptr[]转换成ptr[][][]三维数组
ptr = &&ptr[0] ptr指向的对象是字符串的地址
p是三级指针
*p(*ptr = &ptr[x])是字符串的地址 **p(**p=ptr[x])是字符串内容

函数指针

理解复杂声明可用的“右左法则”:
从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。举例:
int (*func)(int *p);
首先找到变量名func,外面有一对圆括号,而且左边是一个号,这说明func是一个指针;然后跳出这个圆括号,先看右边,又遇到圆括号(只有函数后面才跟形参圆括号),这说明 (func)是一个函数,所以func是一个指向这类函数的指针,即函数指针,这类函数具有int类型的形参,返回值类型是int,此处就是声明函数。

int (* func[5])(int *);
func 右边是一个[]运算符,说明func是具有5个元素的数组;func的左边有一个*,说明func的元素是指针(注意这里的不是修饰func,而是修饰 func[5]的,原因是[]运算符优先级比高,func先跟[]结合)。跳出这个括号,看右边,又遇到圆括号,说明func数组的元素是函数类型的指针,它指向的函数具有int*类型的形参,返回值类型为int。

来源:typedef 百度百科

指向含有8个元素的数组的指针,每个元素是一个函数指针,该函数的返回值是int, 函数参数是int *
int (*(*p)[8])(int *);

零长度数组

零长度数组的英文原名为Arrays of Length Zero,是GNU C的规范,主要用途是用来作为结构体的最后一个成员,然后用它来访问此结构体对象之后的一段内存(通常是动态分配的内存)。什么意思呢?
以struct devres为例,node变量的长度为3个指针的长度,而struct devres的长度也是3个指针的长度。而data只是一个标记,当有人分配了大于3个指针长度的空间并把它转换为struct devres类型的变量后,我们就可以通过data来访问多出来的memory。也就是说,有了零长度数组data,struct devres结构的长度可以不定,完全依赖于你分配的空间的大小。有什么用呢?
来源:Linux设备模型(9)_device resource management

   struct devres {
     struct devres_node    node;
     /* -- 3 pointers */
     unsigned long long    data[]; /* guarantee ull alignment */
	 };

变长数组

变长数组必须是自动存储类,这意味着它们必须在函数内部或作为函数参量声明,而且声明时不可以进行初始化。
变长数组的大小在创建后是保持不变的,“变”得意思是说其维大小可以用变量来指定。
int sum2d(int rows, int cols, int ar[rows][cols]);正确
int sum2d(int ar[rows][cols],int rows, int cols);顺序错误
int sum2d(int, int, int ar[][]);正确
C99标准规定,可以省略函数原型中的名称,但是如果省略名称,则需要用星号来代替省略的维数。

函数定义参量列表中的变长数组声明实际上并没有创建数组。变长数组名实际上市指针,也就是说具有变长数组参量的函数实际上直接使用原数组,因此它有能力修改作为参数传递进来的数组。

来源: 《C Primer Plus》第五版

拓展

增量运算符和减量运算符只能影响一个变量(或者更一般地来讲,一个可修改的左值)。
当n++是表达式的一部分时,您可以认为它表示“先使用n;然后将它的值的增加”,另一方面,++n的意思是“先将n值增加,然后再使用它”
1.如果一个变量出现在同一个函数的多个参数中时,不要将增量或者减量运算符用于它上面
2.如果一个变量多次出现在一个表达式里,不要将增量或减量运算符用于它上面

来源: 《C Primer Plus》第五版


char line[81];
char text[81];

使用typedef:
typedef char Line[81];
Line text,line;

typedef char* pStr1;
#define pStr2 char* 
pStr1 s1,s2;
pStr2 s3,s4;
在上述的变量定义中,s1、s2、s3都被定义为char *,而s4则定义成了char,不是我们所预期的指针变量,根本原因就在于#define只是简单的字符串替换而typedef则是为一个类型起新名字。
上例中define语句必须写成 pStr2 s3, *s4; 这样才能正常执行。

来源:typedef 百度百科

参考资料
[1] C语言中对数组名取地址
[2]数组指针
[3]《C Primer Plus》第五版 [美]Stephen Prata著 云巅工作室译 2013年6月北京第30次印刷
[4]typedef 百度百科

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值