C语言学习日记八
数组和指针
一、数组
- 数组由数据类型相同的同一系列元素组成。
1、初始化数组
char name[10];
,定义了char 类型的数组,其中共有10个元素,name[0]是第一个元素,name[9]是最后一个元素。- 当然也可以这样定义
int powers[8] = {1,2 , 3,4,5,6,7}
,逗号和值之间可以使用空格。 - 使用const关键字可以将数组声明为只读数组。例如
const int days[2] = {1,2};
- 如果部分初始化数组,那么剩余的元素就会被初始化为0。一般是后面的没有赋值。
- 如果初始化数组时省略方括号中的数字,编译器会根据初始化列表中的项数来确定数组的大小。
例如:const int days[] = { 30,31,31,30,30,31,30}
;必须要有方括号才能表示你声明的是数组。
2、给数组元素赋值
- 声明数组后,可以借助数组下标(或索引)给数组元素赋值。例如:
name[5] = 'n';
- C语言不允许把数组作为一个单元赋给另外一数组。
3、数组边界
- 使用数组时,要防止数组越过边界。
二、多维数组
1、初始化二维数组
- 初始化多维数组是建立在初始化一维数组的基础上。
- 初始化多维数组,要用逗号分隔每一行。例如:
const float rain[YEARS][MONTHS] =
{
{4.3,4.3,4.3,3.0,2.0,1.2,0.2,0.2,0.4,2.4,3.5,6.6},
{8.5,8.2,1.2,1.6,2.4,0.0,5.2,0.9,0.3,0.9,1.4,7.3},
{9.1,8.5,6.7,4.3,2.1,0.8,0.2,0.2,1.1,2.3,6.1,8.4},
{7.2,9.9,8.4,3.3,1.2,0.8,0.4,0.0,0.6,1.7,4.3,6.2},
{7.6,5.6,3.8,2.8,3.8,0.2,0.0,0.0,0.0,1.3,2.6,5.2}
};
- 如果第某个列表值不够,那么最后的元素将会被初始化为0。如果超过,那么会报错。
2、其他多维数组
float rain[4][7];
//rain是一个内含4个元素的数组,每个元素又包含7个float类型值的数组。
首元素是rain[0][0],尾元素是rain[3][6]。- 一般处理三维数组要使用3重嵌套循环,处理四维数组要使用4重嵌套循环。依此类推。
三、指针和数组
- 指针提供一种以符号形式使用地址的方法。指针在某种程度上把程序员想要传达的指令以更接近机器的方式表达。数组表示法其实是在变相地使用指针。
- 数组名是数组首元素的地址,即如果name是一个数组,那么该式成立name == &name[0];两者都是常量,在程序的运行过程中不会改变。但是可以把他们赋值给指针变量,然后可以修改指针变量的值来改变数组。
- 在C语言中,指针加1是指的增加一个存储单元。对数组而言,是下一个元素的地址。根据类型的不同,地址增加的多少也不同,如short(short类型占2个字节)移动2个字节,double(double类型占8个字节)移动8个字节。
- 指针的值是它所指对象的地址。使用*运算符可以获得该指针所指向对象的值。
dates + 2 == &dates[2];//相同的地址
*(dates + 2) == dates[2];//相同的值
- 定义ar[n]的意思是*(ar + n)。*(ar + n)的意思是“到内存的ar位置,然后移动n个单元,检索存储在那里的值”。
- 可以用指针表示数组,也可以用数组表示指针。是等效的方法。
四、函数、指针和数组
- 由于函数原型可以省略参数名,故以下四种原型等价:
int sum(int *ar,int n);
int sum(int *,int );
int sum(int ar[],int n);
int sum(int [],int);
- 只有在函数原型或函数定义头中,才可以用int ar[]代替int *ar;
- int *ar形式和int ar[]形式都表示ar是一个指向int的指针
1.指针操作
- 在C语言中,ar[i]和*( ar + i )这两个表达式是等价的。
- 赋值:可以把地址赋给指针。
- 解引用:*运算符给出指针指向地址上存储的值。
- 取址:&运算符给出指针本身的地址。
- 指针与整数相加:整数和指针所指向类型的大小(以字节为单位)相乘,然后把结果与初始地址相加。即ptr1 + 4等价于&ptr[4]
- 递增指针:递增指向数组元素的指针可以让该指针移动至数组的下一元素。
- 指针减去一个整数:该整数将乘以指针指向类型的大小(以字节为单位),然后用初始地址减去乘积。
- 递减指针:递减指向数组元素的指针可以让该指针移动至数组的上一元素。
- 指针求差:计算两个指针的差值,求出两元素之间的距离。差值的单位与数组类型的单位相同。两个都指向相同的数组。
- 比较:使用关系运算符可以比较两个指针的值,前提是两个指针都指向相同类型的对象。
2、初始化指针的注意事项
- 不要解引用未初始化的指针:例如
int * pt;
*pt = 5;
这种写法是错误的,因为指针pt的值是地址,但是没有给它确定的地址,也就是没有初始化。而且创建一个指针时,系统只分配了存储指针本身的内存,并没有分配存储数据的内存。
- 参考代码
// ptr_ops.c -- pointer operations
#include <stdio.h>
int main(void)
{
int urn[5] = {100,200,300,400,500};
int * ptr1, * ptr2, *ptr3;
ptr1 = urn; // assign an address to a pointer
ptr2 = &urn[2]; // ditto
// dereference a pointer and take
// the address of a pointer
printf("pointer value, dereferenced pointer, pointer address:\n");
printf("ptr1 = %p, *ptr1 =%d, &ptr1 = %p\n",
ptr1, *ptr1, &ptr1);
// pointer addition
ptr3 = ptr1 + 4;
printf("\nadding an int to a pointer:\n");
printf("ptr1 + 4 = %p, *(ptr4 + 3) = %d\n",
ptr1 + 4, *(ptr1 + 3));
ptr1++; // increment a pointer
printf("\nvalues after ptr1++:\n");
printf("ptr1 = %p, *ptr1 =%d, &ptr1 = %p\n",
ptr1, *ptr1, &ptr1);
ptr2--; // decrement a pointer
printf("\nvalues after --ptr2:\n");
printf("ptr2 = %p, *ptr2 = %d, &ptr2 = %p\n",
ptr2, *ptr2, &ptr2);
--ptr1; // restore to original value
++ptr2; // restore to original value
printf("\nPointers reset to original values:\n");
printf("ptr1 = %p, ptr2 = %p\n", ptr1, ptr2);
// subtract one pointer from another
printf("\nsubtracting one pointer from another:\n");
printf("ptr2 = %p, ptr1 = %p, ptr2 - ptr1 = %td\n",
ptr2, ptr1, ptr2 - ptr1);
// subtract an integer from a pointer
printf("\nsubtracting an int from a pointer:\n");
printf("ptr3 = %p, ptr3 - 2 = %p\n",
ptr3, ptr3 - 2);
return 0;
}
五、保护数组中的数据
1、对形式参数使用const
- 如果函数的意图不是修改数组中的数据内容,那么在函数原型和函数定义中声明形式参数时应该使用关键字const。示例
int sum(const int ar[],int n);//函数原型
int sum(const int ar[],int n)//函数定义
{
int i;
int total = 0;
for(i = 0;i < n; i++)
total += ar[i];
return total;
}
2、const的其它内容
- 指向const的指针不能用于改变值
int rates[5] = {2,4,3,4,3}
const int * pd = rates;//pd指向数组的首元素
*pd = 10;//不允许使用pd修改它所指向的数据的值。
pd[2] = 5;//不允许
rates[0] = 5;//允许,因为rates没有被const限定
pd++;//可以让pd指向别处
- 把const数据或非const数据的地址初始化为指向const的指针或为其赋值是合法的,然而,只能把非const数据的地址赋给普通指针。
- 创建指针时可以使用const两次,表明该指针既不能更改它所指向的地址,也不能修改指向地址上的值
const float * const pc = rates;
六、指针和多维数组
- 假设有如此声明:
int zippo[4][2];//内含int类型的数组
- zippo的首元素是一个内含两个int值的数组,所以zippo是这个内含两个int值的数组的地址。
- 故zippo的值与&zippo[0]的值相同。即zippo[0](小地址)是一个占用一个int大小对象的地址。而zippo(大地址:小地址)是一个占用两个int大小对象的地址。
- 给指针或地址加1,其中会增加对应类型大小的数值。因为zippo指向的对象占用了两个int大小,而zippo[0]只占用一个int大小。故zippo+1和zippo[0]+1的值不同。
- zippo是地址的地址,必须解引用两次才能获得原始值。地址的地址或指针的指针就是双重间接。
1、指向多维数组的指针
- int (* pz)[2];//pz指向一个内含两个int类型值的数组,[number][2]。
- int * pax[2];//paz是一个内含两个指针元素的数组,每个指针元素都指向int 的指针。
- 示例
/* zippo2.c -- zippo info via a pointer variable */
#include <stdio.h>
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;
}
2、指针的兼容性
- 指针之间的赋值比数值类型之间的赋值要严格许多。两个不同类型的指针不可以互相赋值。
- 指向一个内涵3个int类型元素的数组不能与指向一个内含2个int类型元素的数组兼容。
- 把const指针赋值给非const指针不安全。
七、多维数组与函数
- 如果junk是二维数组,junk[i]就是一维数组,可视为二维数组的一行。
八、变长数组
- C99新增了变长数组,允许使用变量表示数组的维度。
int quarters = 4;
int regions = 5;
double sales[regions][quarters];//一个变长数组
- 变长数组的限制:必须是自动存储类别,意味着无论在函数中声明还是作为函数形参声明,都不能使用static或extern存储类别说明符;不能在声明中初始化它们。
- “变”不是可以修改已经创建的数组的大小。而是在创建数组时,可以用变量指定数组的维度。
九、一些补充
- 在C语言,不能把整个数组作为参数传递给函数,但是可以传递数组的地址。
- 编译器和运行的程序不会检查数组的下标。