1. 数组
-
数组(array)是按顺序存储的一系列类型相同的值 -
整个数组有一个数组名,通过整数下标访问数组中单独的项或元素(element) -
数组的下标从0开始 -
C编译器不会检查数组的下标是否正确 int i[10];
i[20] = 1;
i[23] = 2;
- 编译通过,但是会导致数据被放置在已被其他数据占用的地方,可能会破坏程序的结果甚至导致程序异常中断
-
用于识别数组元素的数字被称为下标(subscript)、索引(indice)或偏移量(offset) -
数组中的元素被依次存储在内存中相邻的位置
1.1 初始化数组
- 只存储单个值得变量有时也称为标量变量(scalar variable)
int a[2] = { 1.0, 2.1 };
- 如果不初始化数组,数组元素和未初始化1得普通变量一样,其中存储的都是垃圾值
- 如果部分初始化数组,剩余的元素都会被初始化为0
#include <stdio.h>
int main() {
int a[2];
for (int i = 0; i < 2; i++)
{
printf("%d\n", a[i]);
}
a[0] = 1;
for (int i = 0; i < 2; i++)
{
printf("%d\n", a[i]);
}
int b[2] = { 1 };
for (int i = 0; i < 2; i++)
{
printf("%d\n", b[i]);
}
}
- 可以省略方括号中的数字,让编译器自动匹配数组大小和初始化列表中的项数
int i = { 1, 2 };
使用 const 声明数组
1.2 指定初始化器(C99)
- C99增加的一个新特性:指定初始化器(designated initializer);利用该特性可以初始化指定的数组元素
int i[3] = { 0, 0, 1 };
int i[3] = { [2] = 1 };
- 指定初始化器的两个重要特性
- 如果指定初始化器后面有更多的值,那么后面这些值将被用于初始化指定元素后面的元素
- 如果在此初始化指定元素,那么最后的初始化将会取代之前的初始化
#include <stdio.h>
int main() {
int a[5] = { 0, 1,[3] = 3, 4,[1] = 2 };
for (int i = 0; i < 5; i++)
{
printf("%d ", a[i]);
}
}
1.3 给数组元素赋值
- 声明数组后,可以借助数组下标(索引)给数组元素赋值
int i[2];
i[1] = 1;
- C不允许把数组作为一个单元赋给另一个数组,除初始化以外也不允许使用花括号列表的形式赋值
int i[2];
i = { 0, 1 };
1.4 数组边界
- 使用数组时,要防止数组下标越界,编译器不会检查越界错误
#include <stdio.h>
int main() {
int ints[5] = { 0, 1, 2, 3, 4 };
int i;
for ( i = -1; i < 5; i++)
{
ints[i] = i;
}
for (i = -1; i < 5; i++)
{
printf("ints[%d] = %d\n", i, ints[i]);
}
}
1.5 指定数组的大小
- 在 C99 标准之前,声明数组时只能在方括号中使用整型常量表达式
- 整型常量表达式:由整型常量构成的表达式
- sizeof 表达式被视为整型常量,但是(与C++不同)const 值不是
- 表达式的值必须大于0
- C99 创建了一种新型数组,称为可变长数组(variable-length array) 或简称 VLA
2. 多维数组
- 数组的数组,主数组(master array) 内含元素,元素是内含元素的数组
int ints[2][3];
2.1 初始化二维数组
#include <stdio.h>
int main() {
int ints[2][3] = { { 1, 2 }, { 3, 4 } };
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
{
printf("ints[%d][%d] = %d ", i, j, ints[i][j]);
}
printf("\n");
}
}
#include <stdio.h>
int main() {
int ints[2][3] = { 1, 2, 3, 4 };
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
{
printf("ints[%d][%d] = %d ", i, j, ints[i][j]);
}
printf("\n");
}
}
2.2 其他多维数组
3. 指针和数组
#include <stdio.h>
int main() {
int ints[2];
double doubles[2];
int* i;
double* d;
i = ints;
d = doubles;
for (int index = 0; index < 2; index++)
{
printf("index = %d, int: %#x, double: %#x\n", index, i + index, d + index);
}
}
- 系统中地址按字节编址,int 类型占用 4 字节,double 类型占用 8 字节;在C中,指针加 1 指的是增加一个存储单元
- 对数组而言,意味着加 1 后的地址是下一个元素的地址,而不是下一个字节的地址
- 即使指针指向的是标量变量,也要知道变量的类型,否则指针就无法正确地取回地址上的值
int ints[2];
ints + 1 == &ints[1];
*(ints + 1) == ints[1];
*(ints + n) == ints[n];
4. 函数、数组和指针
-
使用函数计算数组中元素之和
-
利用数组传入 #include <stdio.h>
#define SIZE 5
int sum(int[]);
int main() {
int ints[SIZE] = { 1, 2, 3, 4, 5 };
int total = 0;
total = sum(ints, SIZE);
printf("total = %d\n", total);
}
int sum(int ints[], int size) {
int total = 0;
for (int i = 0; i < size; i++)
{
total += ints[i];
}
return total;
}
-
利用指针传入 #include <stdio.h>
#define SIZE 5
int sum(int*);
int main() {
int ints[SIZE] = { 1, 2, 3, 4, 5 };
int total = 0;
total = sum(ints, SIZE);
printf("total = %d\n", total);
}
int sum(int* ints, int size) {
int total = 0;
for (int i = 0; i < size; i++)
{
total += ints[i];
}
return total;
}
-
只有在函数原型或函数定义头中,以上两种方式才可相互替换
4.1 使用指针形参
- 函数要处理数组需要知道何时开始,何时结束
- 一种方法:传入数组和数组大小
- 另一种方法:传入数组的开始处和结束处指针
#include <stdio.h>
#define SIZE 5
int sum(int*, int*);
int main() {
int ints[SIZE] = { 1, 2, 3, 4, 5 };
int total = 0;
total = sum(ints, ints + SIZE);
printf("total = %d\n", total);
}
int sum(int* start, int* end) {
int total = 0;
while (start < end)
{
total += *start;
start++;
total += *start++;
printf("total = %d\n", total);
}
return total;
}
4.2 指针表示法和数组表示法
- 指针表示法:
- 更接近机器语言
- 一些编译器在编译时能生成效率更高的代码
- 数组表示法:
- 让函数是处理数组的意图更加明显
- 许多其他语言的程序员对数组表示法更熟悉
5. 指针操作
解引用未初始化的指针
int* i;
*i = 1;
- 创建一个指针时,系统只分配了存储指针本身的内存,并未分配存储数据的内存
- 因此,在使用指针之前,必须先用已分配的地址初始化它
6. 保护数组中的数据
#include <stdio.h>
#define SIZE 5
int sum(int*);
int main() {
int ints[SIZE] = { 1, 3, 5, 7, 9 };
int total = 0;
total = sum(ints, SIZE);
printf("total = %d\n", total);
}
int sum(int* ints, int size) {
int total = 0;
for (int i = 0; i < size; i++)
{
total += ints[i]++;
printf("ints[%d] = %d\n", i, ints[i]);
}
return total;
}
6.1 对形式参数使用 const
#include <stdio.h>
#define SIZE 5
int sum(const int*);
int main() {
int ints[SIZE] = { 1, 3, 5, 7, 9 };
int total = 0;
total = sum(ints, SIZE);
printf("total = %d\n", total);
}
int sum(const int* ints, int size) {
int total = 0;
for (int i = 0; i < size; i++)
{
total += ints[i]++;
printf("ints[%d] = %d\n", i, ints[i]);
}
return total;
}
-
发生对只读对象进行修改的操作时,编译器会捕获这个错误,并生成一条错误信息 -
如果编写的函数需要修改数组,在声明数组形参时则不使用 const -
如果编写的函数不需要修改数组,在声明数组形参时最好使用 const
6.2 const 的其他内容
7. 指针和多维数组
int ints[4][2];
-
对上方多维数组的指针属性分析
-
ints 和 ints[0] 的值相同 ints == &ints[0];
ints[0] == &ints[0][0];
-
给指针或地址加 1,其值会增加对应类型大小的数值 ints + 1 != ints[0] + 1;
-
解引用一个指针或在数组名后使用带下标的 [] 运算符,得到引用对象代表的值 *(ints[0]) == ints[0][0];
ints[0] == &ints[0][0];
*ints == ints[0];
*ints == &ints[0][0];
**ints == ints[0][0];
- ints 是地址的地址,必须解引用两次才能获得原始值
- 地址的地址或指针的指针就是双重间接(double indirection) 的例子
#include <stdio.h>
int main() {
int ints[4][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
printf("ints : %#x\n", &ints);
for (int i = 0; i < 4; i++)
{
printf("ints[%d] : %#x\n", i, &ints[i]);
for (int j = 0; j < 2; j++)
{
printf("ints[%d][%d] : %#x ", i, j, &ints[i][j]);
}
printf("\n");
}
}
- 数组地址、数组内容和指针之间关系的视图(P261)
7.1 指向多维数组的指针
int (*i)[2];
int* i[2];
int** i;
-
二维数组利用指针遍历
-
看成一维数组 #include <stdio.h>
void see(int**, int**);
int main() {
int ints[4][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
see(ints, ints + 4);
}
void see(int** start, int** end) {
while (start < end)
{
printf("%d : %#x\n", *start, start);
start++;
}
}
-
使用二维数组特性 #include <stdio.h>
void see(int*[2], int*[2]);
int main() {
int ints[4][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
see(ints, ints + 4);
}
void see(int(*start)[2], int(*end)[2]) {
int* i;
while (start < end)
{
i = start;
printf("%#x : %#x : %d\n", start, *start, **start);
printf("%#x : %d\n", i, *i);
printf("%#x : %d\n", i + 1, *(i + 1));
start++;
printf("\n");
}
}
7.2 指针的兼容性(P262 有问题)
- 变量可以进行类型转换,指针可以进行类型转换(会被无效)
int i = 1;
float f = 2.0;
int* i_po = &i;
float* f_po = &f;
printf("f = %f, i = %d\n", f, i);
printf("f_po = %#x, i_po = %#x\n", f_po, i_po);
printf("*f_po = %f, *i_po = %d\n", *f_po, *i_po);
f_po = i_po;
printf("f = %f, i = %d\n", f, i);
printf("f_po = %#x, i_po = %#x\n", f_po, i_po);
printf("*f_po = %f, *i_po = %d\n", *f_po, *i_po);
最后一段
#include <stdio.h>
int main() {
int ints[4][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
printf("ints: %#x\n", &ints);
int** i1;
i1 = ints;
printf("i1 = %#x\n", i1);
int(*i2)[2];
i2 = ints;
printf("i2 = %#x\n", i2);
}
7.3 函数和多维数组
#include <stdio.h>
void see(int*[2], int*[2]);
int main() {
int ints[4][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
see(ints, ints + 4);
}
void see(int(*start)[2], int(*end)[2]) {
int n = 1;
int* i;
while (start < end)
{
i = start;
printf("第%d行求和为%d\n", n, *i + *(i + 1));
n++;
start++;
printf("\n");
}
}
#include <stdio.h>
void see(int*[2], int*[2]);
int main() {
int ints[4][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
see(ints, ints + 4);
}
void see(int(*start)[2], int(*end)[2]) {
int* i = start;
int n = 1;
int sum = 0;
int count = 1;
while (start < end)
{
sum += **start;
if (count == 4)
{
printf("第%d列求和为%d\n", n, sum);
if (n != 2)
{
count = 1;
sum = 0;
n++;
i++;
start = i;
}
else
{
break;
}
}
else {
count++;
start++;
}
}
}
8. 变长数组(VLA)
- C99 新增了变长数组(variable-length array, VLA),允许使用变量表示数组的维度
int a = 4;
int b = 2;
int ints[a][b];
- 变长数组不能改变大小,只是在创建数组时,可以使用变量指定数组的维度
- 变长数组允许动态内存分配,可以在程序运行时指定数组的大小
9. 复合字面量
-
C99 新增了复合字面量(compound literal) -
字面量时除符号常量外的常量
- 5 是 int 类型字面量
- 1.2 是 double 类型字面量
- ‘a’ 是 char 类型字面量
- “abc” 是 字符串类型字面量
-
创建 int ints[3] = { 1, 2, 3 };
(int [3]) { 1, 2, 3 };
-
忽略大小 int ints[] = { 1, 2, 3 };
(int []) { 1, 2, 3 };
-
不能先创建然后使用,必须在创建时使用 int* i;
i = (int [3]) { 1, 2, 3 };
-
作为实际参数传参 sum((int [3]) { 1, 2, 3 });
-
复合字面量是提供临时需要的值的一种手段
- 复合字面量具有块作用域,这意味着一旦离开定义复合字面量的块,程序将无法保证该字面量是否存在
- 复合字面量的定义在最内层的花括号中
问题
#include <stdio.h>
void see(int*, int*);
int main() {
int ints[5] = { 1, 3, 5, 7, 9 };
see(ints, ints + 5);
}
void see(int* start, int* end) {
while (start < end)
{
printf("%d\n", *start);
start++;
}
}
#include <stdio.h>
void see(int*);
int main() {
int ints[5] = { 1, 3, 5, 7, 9 };
see(ints);
}
void see(int* ints) {
while (ints < ints + 5)
{
printf("%d\n", *ints);
ints++;
}
}
|