数组、指针、数组指针、指针数组
数组和指针的用法
int num[5] = { 10, 20 ,30 ,40 ,50};
int *p = num;
type | 打印时的值 | 第二次打印的值 | 源地址的值(数组内的值) |
---|---|---|---|
*p++ | 10 | 20 | 10 |
*(p++) |
*和++的优先级相同,根据结合性是从右往左,所以p先和后自增运算符++相结合,++操作会在表达式完成后进行自增,也就是先取出p地址里的值,然后p的下标后移一位
type | 打印时的值 | 第二次打印的值 | 源地址的值(数组内的值) |
---|---|---|---|
*++p | 20 | 30 | 20 |
*(++p) |
*和++的优先级相同,根据结合性是从右往左,所以p先和前自增运算符++相结合,++操作会在表达式执行时立即完成,也就是先把p的下标后移一位,然后再取出这个下标的值
type | 打印时的值 | 第二次打印的值 | 源地址的值(数组内的值) |
---|---|---|---|
*(p++) | 10 | 11 | 11 |
根据优先级,小括号优先级最高,p先和*相结合,立刻取出这个下标的值,然后这个值和后自增运算符++结合,因为是后自增,++操作将在表达式完成后进行自增,也就是先打印p下标的值,然后再将这个值自增1
type | 打印时的值 | 第二次打印的值 | 源地址的值(数组内的值) |
---|---|---|---|
++*p | 11 | 12 | 11 |
++(*p) |
*和++的优先级相同,根据结合性是从右往左,所以p先和取址运算符*相结合, 然后这个值和前自增运算符++相结合,也就是因为是前自增,++操作将在取值完成立即自增,也就是先取出p下标的值,然后再将这个值自增1,同时打印出来
总结
* 如果一个表达式有多个运算符,那么悠闲进行优先级判断,先执行优先级高的运算符
* 如果运算符优先级相同,那么就看结合性,根据结合方向来做运算
结合性
* 从左往右:简称左结合,变量名和表达式在运算符两侧,运算顺序是从左向右的(小括号 () 、中括号 [] 、成员选择 , 、成员选择 -> 、双目运算符 、逗号运算符)
* 从右往左:简称右结合,变量名和表达式在运算符两侧,运算顺序使从右往左的(单目运算符 、三目运算符 、赋值类运算符)
指针数组 和 数组指针
指针数组
- 定义形式
int *p[n] = { 0 };
根据优先级,[]的优先级高于*,所以 p 先和 [n] 相结合,说明 p 是一个数组名,然后再和 int* 结合,说明这个数组里每个元素都是一个 int 类型的指针
示例
* 使用指针数组接收多个数据的地址
#include <stdio.h>
int main(void)
{
int a[3] = {10, 20, 30};
int *p[3];
for(int i = 0; i < 3; ++i)
{
p[i] = &a[i];
}
for(int i = 0; i < 3; ++i)
{
printf("%p\n", p[i]); // 打印数组内每个元素的值,也就是 a 的每个元素的地址
printf("%d\n", *(p[i])); // 取出地址里的值,也就是数组 a 的每个元素的值
}
}
- 使用指针数组存储多个字符串
#include <stdio.h>
int main(void)
{
char *str[5] =
{
"ISO/IEC9899:2011",
"Programming",
"Dennis Ritchie",
"C",
"Beol Labs"
};
char *str1 = str[1]; // 定义一个字符指针,指向指针数组 str 第二个字符串的地址
char *str2 = *(str +3); // 定义一个字符指针,指向指针数组 str 第四个字符串的地址
char ch1 = *(*(str + 4) + 2); // 定义一个字符变量,接受了指针数组第五个字符串的第三个字符
char ch2 = (*str + 5)[7]; // 定义一个字符变量,接收指针数组第一个字符串的第 5+7 个字符
char ch3 = *str[0] + 6; // 定义一个字符变量,接收指针数组第一个字符串的第一个字符,ASCII再加上6
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
printf("ch1 = %c\n", ch1);
printf("ch2 = %c\n", ch2);
printf("ch3 = %c\n", ch3);
return 0;
}
输出结果为
str1 = Programming
str2 = C
ch1 = o
ch2 = 2
ch3 = O
数组指针(行指针)
- 定义形式
int (*p)[n];
int a[3][4] = { 0 };
int (*p)[4] = a;
int a[3][4][5] = { 0 };
int (*p)[4][5] = a;
() 和 [] 优先级相同,但是结合性是从左往右,所以 p 先和 * 相结合,说明 p 是一个指针,在和 int[] 相结合,说明这个是一个指向 int 类型数组的指针
示例
void func(int p[2][3], int n);
void func(int p[][3], int n);
void func(int (*p)[3], int n);
{
printf("%d\n", p[1][2]); // 打印第二行第三个元素的值 : 60
printf("%d\n", **p); // p 是行指针,*p是取出当前行当前列的地址,再通过*取值运算符,取取出这一列下标的值 : 10
printf("%d\n", (*p + 1)[2]); // 先取出列地址,往后移动一位(一个int长度),再以这个列地址为行首,取出这一行的第三个元素的值 : 40
printf("%d\n", *(p + 1)[0]); // 先将行指针往后移动一位(三个int长度),再得出这一行的第一列元素的地址,再通过取址运算符*,得出这一列的值: 40
printf("%d\n", *(*p + 1)); // 先取出列地址,往后移动一位(一个int长度),再取出这一列的值: 20
printf("%d\n", *(p[1] + 2)); // 先取出p[1]的列地址,往后移动二位(二个int长度),在取出这一列的值: 60
printf("%d\n", *(*(p + 1)));
// 先把行指针后移一位(三个int长度),然后取出这一列的地址,再通过这个列地址取出里面的值: 40
printf("%d\n", *((*p + 1) + 2)); // 先取出列下标的地址,往后移动一位(一个int),再往后移动二位(二个int),再取出这个下标的值:40
printf("%d\n", *(*p + 1) + 2); // 先取出列下标的地址,往后移动一位(一个int),再取出这个列元素的值,再把这个值加上2: 22
}
int main(void)
{
int a[2][3] = {{10, 20, 30}, {40, 50, 60}};
func(a, sizeof(a) / sizeof(int));
return 0;
}
总结
* 指针数组:看后面两个字,就是一个数组,这个数组每个元素都是一个指针,这个数组在内存空间里占了 n 个指针大小的储存空间
* 数组指针:看后面两个字,就是一个指针,这个指针指向了一个数组,这个指针在内存空间里占了 1 个指针大小的存储空间
* 二级指针p 和 二维数组p 的区别:
int **p = NULL; 是一个整型的二级指针,p 是可变的变量,我们可以让它指向任何我们想让它指向的地方这个变量只占 4或8 个字节,所以不需要单独指定大小
int p[n][m] = {0}; 是一个整型的二维数组,p 指向一块连续的内存空间的首地址,我们可以用 p 找到这块内存空间,这块空间占用了 sizeof(int) * n * m 个字节大小
p 是一个不可变的常量,只能永远的指向这块内存