1.字符指针
(注:以下部分截图来源b站鹏哥C语言教学视频)
char *p = "abcdef" 是常量字符串,一样的常量字符串在内存中只需要存储一份,所以指针指向同一个地址。
新手常见疑惑:
char* arr = "abc"; (字符指针 arr 存的是字符 ‘a’ 的地址)
char arr[] = "abc";
这两种写法有什么区别?以下是智谱清言的回答:
在C语言中,char* arr = "abc"; 和 char arr[] = "abc"; 这两种写法在表面上看起来非常相似,但它们在内存分配和字符串的存储方式上有所不同:
1.har* arr = "abc";
- 这里的arr是一个指向字符常量区的指针。"abc"是一个字符串字面量,它存储在程序的常量区,这个区域的内存通常是只读的。
- arr指针本身存储在栈上,但它指向的字符串"abc"是存储在只读数据段中的。
- 由于字符串是常量,所以你不能通过arr指针来修改字符串的内容,比如arr[0] = 'x';这样的操作是未定义行为,可能会导致程序崩溃。
2.char arr[] = "abc";
- 这里的arr是一个字符数组,它在栈上分配了足够存储字符串"abc"加上一个额外的空字符'\0'(字符串结束标志)的内存空间。
- 数组arr的内容是可以修改的,你可以改变数组中的任何一个字符,例如arr[0] = 'x';是合法的。
- 当你使用这种写法时,实际上是在栈上复制了字符串"abc"的值。
总结一下,主要区别在于:
- char* arr = "abc"; 是一个指针,指向一个只读的字符串常量。
- char arr[] = "abc"; 是一个数组,它包含了字符串的副本,并且是可以修改的。
2.指针数组
(本质是数组,作用是存储指针)
3.数组指针
(补充说明:数组名指的是数组首元素的地址,&数组名 取出来的是该数组的地址(整个数组的地址))
4.数组参数、指针参数
(数组指针是指向数组的指针,函数指针是指向函数的指针)
5.函数指针
(用来存放函数的地址)(在函数指针中,&函数名 和 函数名一样,都是函数的地址)
如上图,就是一个函数指针,与数组指针有相似的地方,括号内的(int x,int y)这个“x,y”可写可不写,只要函数指针的括号内把函数需要的参数类型写对了就行。
而且,对于函数指针,下面两种调用函数的方式都是可以的,相当于 * 在这里并没有实际用处,两种都是可以理解的,我个人更倾向于使用第二种写法。
可以尝试分析以下两个代码:
由于上图代码二不好理解所以可以通过 typedef 来重新定义,但是重新定义的时候需要注意新定义的函数名字需要贴放在 * 后面。
6.函数指针数组
(“ *parr[4] ”中 parr 先和 [4] 结合,是一个数组,)
函数指针可以用来实现转移表。(如以下代码)
(假设我们已经定义好了Add,Sub等 4 个函数)
int main()
{
int x = 0;
int y = 0;
int input = 0;
int (*pfArr[])(int, int) = { 0,Add,Sub,Mul,Div };
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
printf("%d\n", pfArr[input](x,y));
}
else if(input == 0)
{
printf("退出\n");
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
7.指向函数指针数组的指针
8.回调函数
特别补充说明:
void* :
void* 类型的指针 可以接收任意类型的地址。
void* 类型的指针,不能进行解引用操作,同时也不饿能进行 + - 整数的操作。
回调函数可以参考以下 qsort 函数,这个函数可以排序任何类型的数组。(具体使用可以看鹏哥2020年视频指针详解(6))
(strcmp函数是通过 ASCii 表比较首字母大小,首字母大小相同再比较下一个字母,依次比较)
9.指针和数组的解析
一维数组:
(数组名是首元素地址,只有两个例外( sizeof(数组名) 和 &数组名 ),如下图)
二维数组:
这其中第四个与倒数第四个的区别:
第四个:a 是一个二维数组,a [0] 取出来的是 a 的第一个元素,即 a 的第一行,一个一维数组,取出来就是这个一维数组的数组名,而数组名在非特殊情况下都是代表首元素地址,即 a 的第一行的第一个元素的地址,此时 +1,就是 a 的第一行第一个元素地址 +1,也就是 a 的第一行第二个元素的地址。
倒数第四个:a 是一个二维数组,a [0] 取出来的是 a 的第一个元素,即 a 的第一行,一个一维数组,取出来就是这个一维数组的数组名,而 & (数组名) 取出来的是一整个数组的地址,在一整个数组的地址后面 +1,就是跳过这一整个数组,也就是 a 的下一行,即二维数组 a 的第二行。
数组名的意义
二维数组可能存在的疑惑
一串代码解决二维数组所有问题:
int arr[5][4] = { 0 };
int arr2[4] = { 0 };
printf("%p\n%p\n%p\n%p\n%p\n%p \n", arr[0], arr, arr[1], arr[0] + 1, arr + 1, arr[1] + 1);
printf("\n%p\n%p\n%p\n", arr2, arr2 + 1, &arr2 + 1);
以下是在我电脑上运行的结果:
代码执行如上图,也就是说,在二维数组中,“arr[0]” 与 arr 第一行的一维数组(二维数组每一行都看成一个一维数组)的数组名等价,此时执行 +1,就是将指针后移一位 int 类型大小的空间,而 “arr” 则是与 &第一行数组名 等价,此时为一整个一维数组的地址,此时执行 +1,就是将指针跳过整个一维数组,此时指针指向第二行。
部分习题
下图(题目在左,解析在右)需要注意,小端存储
下图,注意避坑(小括号内为逗号表达式)
逗号表达式:逗号表达式是C语言提供一种特殊的运算符——逗号运算符。它的优先级别最低,它将两个及其以上的式子联接起来,从左往右逐个计算表达式,整个表达式的值为最后一个表达式的值。
下图,如图所示,输出结果是 0xFF FF FF FC -4
需要注意 int (*p) [4] 类型的指针,加一后一次跳过 4 个 int 类型大小的地址。
而负数在内存中以补码的形式存储,直接 %p 当作地址来读取的时候依旧是补码的形式。