一:数组
同一类型的变量——元素(element)集中在一起,在内存上排列成一条直线,这就是数组(array)。
1.1 一维数组
1.1.1 一维数组的声明
int arr1[10];
int arr2[2 + 8];
#define N 10
int arr3[N];
int count = 10;
int arr4[count];
// 在C99标准之前,[]中间必须是常量表达式
// C99标准支持了变长数组的概念,数组大小可以是变量
// 变长数组不能初始化
char arr5[10];
float arr6[1];
double arr7[20];
1.1.2 一维数组的初始化
用0对没有赋初始值的元素进行初始化。'/0'的ASCII码值是0。
int arr1[10] = { 1,2,3 }; // 不完全初始化,剩余的元素默认初始化为0
int arr2[10] = { 0 }; // 所有元素都为0
int arr3[] = { 1,2,3,4 }; // 没有指定数组的大小,数组的大小根据初始化的内容来确定
int arr4[5] = { 1,2,3,4,5 };
char arr5[3] = { 'a',98,'c' };
char arr6[10] = { 'a','b','c' }; // a b c /0 /0 /0 /0 /0 /0 /0
char arr7[5] = "abc"; // a b c /0 /0
// 不能通过赋值语句进行初始化,错误写法:
int arr8[3];
arr8 = { 1,2,3 };
C99增加了一个新特性:指定初始化器(designated initializer)。利用该特性可以初始化指定的数组元素。
// 顺序初始化
int arr[6] = { 0,0,0,0,0,80 };
// 指定初始化
int arr[6] = { [5] = 80 }; // 把arr[5]初始化为80,未初始化的元素都为0
#include <stdio.h>
int main()
{
int arr[10] = { 5,6,[4] = 8,9,7,1,[9] = 3 };
for (int i = 0; i < 10; i++)
{
printf("arr[%d] = %d\n", i, arr[i]);
}
return 0;
}
1.1.3 一维数组的使用
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
// 下标从0开始 0 1 2 3 4 5 6 7 8 9
// 计算数组元素的个数
int sz = sizeof(arr) / sizeof(arr[0]);
// 遍历一维数组
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
1.1.4 一维数组在内存中的存储
一维数组在内存中是连续存储的。
1.2 二维数组
以一维数组作为元素的数组是二维数组,以二维数组为元素的数组是三维数组……统称为多维数组。
1.2.1 二维数组的声明
int arr1[3][4]; // [行][列]
char arr2[3][5];
double arr3[2][4];
1.2.2 二维数组的初始化
int arr1[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };
// 1 2 3 4
// 2 3 4 5
// 3 4 5 6
int arr2[3][4] = { {1,2},{3,4},{5,6} };
// 1 2 0 0
// 3 4 0 0
// 5 6 0 0
int arr3[][2] = { 1,2,3,4 }; // 二维数组如果有初始化,行数可以省略,列数不能省略
// 1 2
// 3 4
指定初始化器对多维数组也有效。
#include <stdio.h>
int main()
{
int arr[4][4] = { 5,6,[1][3] = 8,9,7,1,[3][2] = 3 };
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
printf("arr[%d][%d] = %d\n", i, j, arr[i][j]);
}
}
return 0;
}
1.2.3 二维数组的使用
#include <stdio.h>
int main()
{
int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };
for (int i = 0; i < 3; i++)
{
// 打印一行
for (int j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n"); // 打印一行后换行
}
return 0;
}
// 1 2 3 4
// 2 3 4 5
// 3 4 5 6
1.2.4 二维数组在内存中的存储
二维数组在内存中也是连续存储的。
二维数组X按行顺序存储,其中每个元素占1个存储单元。若X[4][4]的存储地址为0xf8b82140,X[9][9]的存储地址为0xf8b8221c,则X[7][7]的存储地址为?
假设二维数组X有m行n列,第一个元素即X[0][0]的存储地址为start,则:
X[4][4]的存储地址=0xf8b82140=start+4*n*1+4*1 ①
X[9][9]的存储地址=0xf8b8221c=start+9*n*1+9*1 ②
②-①:5n+5=0xdc -> 5n=0xd7=215 -> n=43
X[7][7]的存储地址=start+7*n*1+7*1=(start+4*n*1+4*1)+3*n+3=0xf8b82140+132=0xf8b82140+0x84=0xf8b821c4
1.3 数组名
数组名表示数组首元素的地址,是一个常量指针,不可以改变指针本身的值,没有自增、自减等操作。
数组名和指向数组首元素的指针都可以通过改变偏移量来访问数组中的元素,但数组名是常量指针,指向数组首元素的指针是一般指针。
以下2种情况下数组名表示整个数组:
- sizeof(数组名),计算整个数组的大小,单位是字节。
- &数组名,取出的是数组的地址。
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr); // 0096F7CC 数组首元素的地址
printf("%p\n", arr + 1); // 0096F7D0 指针+1跳过4个字节
printf("%p\n", &arr[0]); // 0096F7CC 数组首元素的地址
printf("%p\n", &arr[0] + 1); // 0096F7D0 指针+1跳过4个字节
printf("%p\n", &arr); // 0096F7CC 数组的地址
printf("%p\n", &arr + 1); // 0096F7F4 指针+1跳过整个数组的大小(40个字节)
return 0;
}
当数组名作为函数参数传递时,就失去了原有特性,退化为一般指针。此时,不能通过sizeof运算符获取数组的长度,不能判断数组的长度时,可能会产生数组越界访问。因此传递数组名时,需要一起传递数组的长度。
error
// err
void test(int arr[10])
{}
void test(int arr[])
{}
void test(int* arr)
{}
int main()
{
int arr[10] = { 0 };
test(arr);
return 0;
}
ok
// ok
void test(int arr[10], int n)
{}
void test(int arr[], int n)
{}
void test(int* arr, int n)
{}
int main()
{
int arr[10] = { 0 };
int n = sizeof(arr) / sizeof(arr[0]);
test(arr, n);
return 0;
}
二维数组是一维数组的数组,二维数组的数组名也表示数组首元素(第一个一维数组)的地址。
二: 指针
2.1 指针和指针变量
指针是内存地址。指针变量是用来存放内存地址的变量,但我们叙述时常把指针变量简称为指针。
#include <stdio.h>
int main()
{
int a = 10; // 在内存中开辟一块空间
int* p = &a; // &操作符取出a的地址
// a占用4个字节的空间,这里是将a的4个字节的第1个字节的地址存放在p中,p就是一个指针变量
return 0;
}
在32位的机器上,地址是由32个0或者1组成的二进制序列,用4个字节的空间来存储,所以一个指针变量的大小是4个字节。
在64位的机器上,地址是由64个0或者1组成的二进制序列,用8个字节的空间来存储,所以一个指针变量的大小是8个字节。
2.2 指针类型
int*类型的指针存放int类型变量的地址
char*类型的指针存放char类型变量的地址
2.2.1 指针加减整数
指针的类型决定了指针的步长(加减一操作的时候,跳过几个字节)。
int*类型的指针加减一跳过4个字节,char*类型的指针加减一跳过1个字节
#include <stdio.h>
int main()
{
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
printf("%p\n", &n); // 000000DADACFF4E4
printf("%p\n", pc); // 000000DADACFF4E4
printf("%p\n", pc + 1); // 000000DADACFF4E5
printf("%p\n", pi); // 000000DADACFF4E4
printf("%p\n", pi + 1); // 000000DADACFF4E8
return 0;
}
2.2.2 指针的解引用
指针的类型决定了对指针解引用的时候有多大的权限(能访问几个字节)。
int*类型的指针解引用能访问4个字节,char*类型的指针解引用能访问1个字节
利用int*类型的指针强制转换成char*类型后只能访问1个字节,来判断当前计算机是大端模式还是小端模式:
#include <stdio.h>
int check_sys()
{
int a = 1;
return *(char*)&a;
}
int main()
{
int ret = check_sys();
if (