指针的进阶
- 字符指针
- 数组指针
- 指针数组
- 数组传参和指针传参
- 函数指针
- 函数指针数组
- 指向函数指针数组的指针
- 回调函数
- 指针和数组面试题解析
声明:该文是学习C语言进阶时的笔记。学习内容:B站鹏哥C语言,p34-p51部分。文中有任何不懂的地方可以观看视频。
指针的基本概念
- 指针就是一个变量用来存放地址,地址是内存空间的唯一标识。
- 指针的大小是固定的4或8字节(32位或64位平台上)。
- 指针是有类型一说的,指针类型决定了对地址访问的步长,也就是指针解引用操作时候的权限。
- 指针的运算。
字符指针
使用形式:
int main(){
char ch = 'w';
char* pc = &ch;
*pc = 's';
return 0;
}
字符数组用来存字符串时:
int main(){
char arr[]="abcdef";
char* pc = arr;
return 0;
}
其他情况:(注意观察字符串和字符输出格式和指针的调用方式)。
int main(){
const char* p = "abcdef";
printf("%c\n",*p);
printf("%s",p);
return 0;
}
解析:如果将第一个printf中的*去掉会警告 Format specifies type ‘int’ but the argument has type ‘char *’。 如果将第二个printf中p改为*p会警告 Format specifies type ‘char *’ but the argument has type ‘char’。 p表示的是地址内容。*p表示的是地址内容指向的东西。
题:
int main() {
char arr1[] = "abcdef";
char arr2[] = "abcdef";
if (arr1 == arr2) {
printf("hehe");
} else {
printf("haaa");
}
return 0;
}
解析:答案是haaa;因为arr1、arr2存放的是首元素的地址,而第一个abcdef和第二个abcdef是存放在不同位置的字符串。
int main() {
char* p1 = "abcdef";
char *p2 = "abcdef";
if (p1 == p2) {
printf("hehe");
} else {
printf("haaa");
}
return 0;
}
解析:这次的答案是hehe;因为这次的"abcdef"是常量,在内存中仅存储了一份。所以p1和p2指向的其实是同一个地方。biao
标准的写法是
const char* p1 = "abcdef";
const char *p2 = "abcdef";
指针数组
其实是数组
int* parr[] = {arr1,arr2,arr3};
基本使用
int main() {
int arr1[] = {1, 2, 3, 4, 5};
int arr2[] = {2, 3, 4, 5, 6};
int arr3[] = {3, 4, 5, 6, 7};
int* parr[] = {arr1,arr2,arr3};
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 5; ++j) {
printf("%d ", *(parr[i])+j);
}
printf("\n");
}
return 0;
}
int* arr1[10];
char* arr2[4];
char** arr3[8];
数组指针
数组指针是什么?是指针。
整形指针,是能够指向整型的指针。浮点型指针,是能够指向浮点型的指针。
那么数组指针就是要能指向数组的指针。
定义方式
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int (*p)[10] = &arr ;
首先p和*结合,说明是一个指针,然后指向的是一个大小为10 的整型的数组,所以p是一个指针指向数组。就是数组指针。
小练习
int main(){
char* arr[5];
pa = &arr;
}
int main(){
char* arr[5];
char* (*pa)[5] = &arr;
return 0;
}
数组指针要素:1. 是一个指针 2. 指针名 3. 指针指向数组的大小 4. 指针指向元素的类型。
数组名 和 &数组名
对于int arr[10];arr和&arr分别是什么。数组名表示数组首元素的地址。&数组表示整个数组的地址。
所以就可以将&数组名存入数组指针当中去。
数组指针使用理解代码(实际不这样使用)
int mian(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int (*pa)[10] = &arr;
for(int i = 0;i<10;i++){
printf("%d ",*(*pa+i));
}
for(int i = 0;i<10;i++){
printf("%d ",(*pa)[i]);
}
return 0;
}
实际使用中一般在二维数组以上。
void print(int (*p)[5],int x,int y){
int i = 0;
for (i = 0; i < x; ++i) {
int j = 0;
for (j = 0; j < y; ++j) {
printf("%d ",*(*(p+i)+j));
}
printf("\n");
}
}
int main(){
int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
print(arr,3,5);
return 0;
}
数组的四种访问形式
int main() {
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int i = 0;
int *p = arr;
for (i = 0; i < 10; ++i) {
printf("%d", p[i]);
printf("%d", *(p + i));
printf("%d", *(arr + i));
printf("%d", arr[i]);
}
return 0;
}
小练习
解释下面的代码的意思。
int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];
解析:第一行:arr是一个有5个元素的整型数组。 第二行:parr1是一个数组,该数组有10个元素,每个元素的类型时int*,综述parr1是一个指针数组。 第三行:parr2是一个指针,指针指向一个数组,数组有10个元素,每个元素的类型是int,综述,parr2是一个数组指针。 第四行:parr3是一个数组,该数组有10个元素,每个元素是一个数组指针,该数组指针指向的数组有5个元素,每个元素是int类型。(图解)
数组参数、指针参数
在函数设计的时候难免会把数组或指针传给函数,那么函数应该怎么接收呢?
一维数组传参
怎么接收参数呢?
int main(){
int arr[10];
int* arr2 = {0};
test(arr);
test2(arr2);
return 0;
}
test()接收参数
void test(int arr[]){}
void test(int arr[10]){}
void test(int *arr){}
test2()接受参数
void test2(int *arr[20]){}
void test2(int *arr[]){}
void test2(int **arr){}
二维数组传参
怎么接收参数呢?
int main(){
int arr[3][5] = {0};
test(arr);
return 0;
}
test()接受参数
void test(int arr[3][5]){}
void test(int arr[][5]){}
因为在二维数组中,可以不知道有多少行,但是必须知道一行多少元素。
接受参数的为指针的形式
void test(int (*arr)[5]){}
不能直接使用一级指针,也不能直接使用二级指针。也不能直接用第一个数组的第一个元素的地址。 指针参数应该指向首元素的地址,二维数组的首元素是一个数组。
一级指针传参
接收参数
void test(int* p){}
void test2(char* p){}
怎么传参呢?倒着往回推:函数接收指针可以向函数传地址或者存放指针的变量。同时还要注意指针的类型。
int main(){
int a = 10;
int* p1 = &a;
test1(&a);
test1(p1);
char ch = 'h';
char* pc = &ch;
test2(&ch);
test2(pc);
return 0;
}
二级指针传参
自定义函数形式,同时理解怎么接收参数。
void test(int** p){}
怎么给函数传参呢?思考什么是二级指针,二级指针的表现形式。 解析:二级指针变量,一级指针的地址,一级指针数组的数组名。
int main(){
int *p;
int **pp = &*p;
int* arr[10];
test(&*p);
test(pp);
test(arr);
}
函数指针
数组指针是指向数组的指针,函数指针是指向函数的指针。是存放函数的地址的一个指针。
int Add(int x,int y){
int z =0;
z = x + y;
return z;
}
int main(){
int a = 10;
int b = 20;
printf("%p\n",&Add);
printf("%p\n",Add);
return 0;
}
我们发现这就像数组名 和 &数组名是一样的道理。函数名和&函数名都是函数的地址。
对于数组指针我们有
int arr[10] = { 0 };
int (*p)[10] = &arr;
那么函数指针是怎么回事呢?
int Add(int x,int y){}
int (*pa)(int,int) = Add;
函数指针的使用
int Add(int x,int y){
int z =0;
z = x + y;
return z;
}
int main(){
int a = 10;
int b = 20;
int (*pa)(int,int) = Add;
printf("%d\n",(*pa)(2,3));//(*pa)==Add
return 0;
}
当然不同类型的函数指针定义的方式是不同的。
使用实例2
void Print(char* str){ printf("%s\n",str);}
int main(){
void (*p)(char*) = Print;
(*p)("hello world");
return 0;
}
简单分析一下:pfun1和pfun2哪个可以存放函数的地址。
void (*pfun1)();
void* pfun2();
解析:首先第一步,要存放地址,需要找什么,需要找指针,哪个是指针,pfun1是指针。
pfun1存放的是指针。pfun1先和*结合。指针指向的是一个函数,这个函数是没有参数的,这个函数的返回值类型是void(相当于没有返回值。)。
阅读有趣的代码
(*(void (*)())0)();
void (*signal(int, void(*)(int)))(int);
解析:代码1,首先void (*p)()这是一个函数指针,p是这个函数指针的变量名。void (*)()这是一个指针函数的类型。在0前面的括号中放一个类型,那么就是对0进行强制类型转化,转化成一个函数指针的地址。然后再用*解引用。解引用的就是void (*)()类型的函数。(*(类型)0)()。这样理解下来就是调用0地址处的函数。
代码2, void(*)(int)是一个函数指针类型,这个函数接收的参数是int类型。signal(int, void(*)(int))这是一个函数的声明,里面有两个参数,一个是int类型,一个是 void(*)(int)。(*signal(int, void(*)(int)))(int)所以这又是一个函数指针参数是int,返回类型是void。
typedef void(*pfun_t)(int);
pfun_f signal(int,pfun_f);
对代码2的综述:signal是一个函数声明;signal函数的参数有2个,第一个是int。第二个是函数指针,该函数指针指向的函数的参数是int,返回类型是void;signal函数的返回类型也是一个函数指针:该函数指针指向的函数的参数是int,返回值是void。
函数指针补充
int Add(int x,int y){
int z =0;
z = x + y;
return z;
}
int main(){
int a = 10;
int b = 20;
int (*pa)(int,int) = Add;
printf("%d\n",(pa)(2,3));
printf("%d\n",(*pa)(2,3));
printf("%d\n",(**pa)(2,3));
return 0;
}
三种调用形式都是同样的正确的输出结果。那么这里的*就没有起到解引用的作用。但是我们一般使用前两种,比较好理解:第一种,pa==Add,Add本身就相当于一级指针,可以直接使用。第二种*pa解引用变量名使用。这里的*其实就是个摆设而已。但是注意的是如果有*这个括号是必须加的。
函数指针数组
数组是一个存放在相同类型数据的存储空间,我们可不可以将函数指针存放在里面呢?
指针数组我们已经学过了,那么函数指针也存在数组中是不是就和指针函数一个道理了。
那么怎么实现呢?
int (*parr[10])();
parr先和[10]结合,说明parr是一个数组,int(*)()是一个函数指针。
函数指针的用途:转移表。
int Add(int x, int y) { return x + y; }
int Sub(int x, int y) { return x - y; }
int Mul(int x, int y) { return x * y; }
int Div(int x, int y) { return x / y; }
int main() {
int (*parr[4])(int, int) = {Add, Sub, Mul, Div};
for (int i = 0; i < 4; ++i) {
printf("%d\n", parr[i](2, 3));
}
return 0;
}
小练习
char* my_strcpy(char* dest,const char* src){}
- 写一个函数指针pf,能够指向my_strcpy
- 写一个函数指针数组pfArr,能够存放4个my_strcpy函数的地址
char* (*pf)(char* ,const char*);
char* (*pfArr[4])(char* ,const char*) = {my_strcpy};
微型计算器
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
int mul(int a, int b) {
return a * b;
}
int div(int a, int b) {
return a / b;
}
int main() {
int x, y;
int input = 1;
int res = 0;
do {
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div 0.exit\n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input) {
case 1:
printf("输入操作数:");
scanf("%d %d", &x, &y);
res = add(x, y);
printf("res = %d\n", res);
break;
case 2:
printf("输入操作数:");
scanf("%d %d", &x, &y);
res = sub(x, y);
printf("res = %d\n", res);
break;
case 3:
printf("输入操作数:");
scanf("%d %d", &x, &y);
res = mul(x, y);
printf("res = %d\n", res);
break;
case 4:
printf("输入操作数:");
scanf("%d %d", &x, &y);
res = div(x, y);
printf("res = %d\n", res);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
int mul(int a, int b) {
return a * b;
}
int div(int a, int b) {
return a / b;
}
int main() {
int x, y;
int input = 1;
int res = 0;
int (*p[5])(int x, int y) = {0, add, sub, mul, div};
while (input) {
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请选择:");
scanf(" %d", &input);
if ((input <= 4 && input >= 1)) {
printf("输入操作数:");
scanf("%d %d", &x, &y);
res = (*p[input])(x, y);
printf("res = %d\n", res);
} else if(input == 0) {
printf("退出程序");
}
else printf("输入有误\n");
}
return 0;
}
指向函数指针数组的指针
数组+指针 --> 数组指针 函数+指针 --> 函数指针 函数指针+数组 --> 函数指针数组 函数指针数组+指针 --> 指向函数指针数组的指针
指向函数指针数组的指针是一个指针。指针指向的是一个数组,数组的元素都是函数指针。
void test(char* str){ printf("%s\n",str); }
int main(){
void (*pf)(char*) = test;
void (*pfArr[4])(char*);
pfArr[0] = test;
void (*(*ppfArr)[4])(char*) = &pfArr;
return 0;
}
void (*(*ppfArr)[4])(char*) = &pfArr;重新解析:(*ppfArr)是一个指针,(*ppfArr)[4]指向的是一个数组,(*(*ppfArr)[4])是一个函数指针数组,数组中每个函数的参数是(char*),返回值是void。
回调函数
回调函数就是一个通过指针调用的函数。如果把函数的指针(地址)作为参数传递给另一个函数,当这个函数被用来调用其所指向的函数的时候,我们就是说这是回调函数。回调函数不是由该函数的实现方直接调用,而是特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。
在微型计算器的第一版中,switch语句中其实比较冗余,冗余的部分就是除了调用函数的其他语句,那么我们可不可以把这些冗余的部分封装成一个函数呢?然后函数调用的部分来用指针访问。
微型计算器
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
int mul(int a, int b) {
return a * b;
}
int div(int a, int b) {
return a / b;
}
void Calc(int (*pf)(int,int)){
int x = 0;
int y = 0;
printf("输入操作数:");
scanf("%d %d", &x, &y);
printf("res = %d\n", pf(x,y));
}
void Calc(int (*pf)(int, int)) {
int x = 0;
int y = 0;
printf("输入操作数:");
scanf("%d %d", &x, &y);
printf("res = %d\n", pf(x, y));
}
int main() {
int x, y;
int input = 1;
int res = 0;
do {
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div 0.exit\n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input) {
case 1:
Calc(add);
break;
case 2:
Calc(sub);
break;
case 3:
Calc(mul);
break;
case 4:
Calc(div);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
Calc()函数接收传过来的指针参数,然后再通过这个参数调用传过来的那个函数的功能,那么传过来(被调用功能)的函数就被叫做回调函数。
用qsort理解回调函数
- 冒泡函数回顾
void bubble_sort(int arr[],int sz){
for(int i = 0; i<sz-1;i++){
int isSort = 1;
for(int j = 0;j<sz-i-1;j++){
if(arr[j]>arr[j+1]){
int temp = arr[j];
arr[j]=arr[j+1];
arr[j+1] = temp;
isSort = 0;
}
}
if (isSort == 1) break;
}
}
int main(){
int arr[10] = {9,8,7,6,5,4,3,2,1,0};
int sz = sizeof(arr)/sizeof(arr[0]);
bubble_sort(arr,sz);
int i = 0;
for(i = 0;i < sz; i++){
printf("%d",arr[i]);
}
}
在原来我们可能觉得使用起来是够用的,但是我们仔细想一想,如果说我们传入的是一个浮点数,或者我们传入的是结构体,我们还要重新写一次函数吗?我们肯定是不愿意的。那有什么办法呢?这时我们想到了,在做微型计算器的时候我们使用了回调函数的概念。这里可不可以使用呢?
在解决上面的问题之前,我们先来看看别人是怎么做的。
- qsort()库函数,使用的是快速排序。
void qsort (void* base, size_t num, size_t size,
int (*compar)(const void*,const void*));
void*类型的指针可以接收任意类型的地址。但是void类型的指针是不能进行解引用操作的。这种类型的指针也不能进行加减整数的运算。所以需要运算的时候可以进行强制类型转化。
qsort()函数的使用
#include <stdlib.h>
#include <string.h>
typedef struct Stu {
char name[20];
int age;
} Stu;
int compare_int(const void *elem1, const void *elem2) {
return *(int *) elem1 - *(int *) elem2;
}
void test1() {
int arr[10] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), compare_int);
int i = 0;
for (i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int compare_float(const void *elem1, const void *elem2) {
return (int) (*(float *) elem1 - *(float *) elem2);
}
void test2() {
float arr[] = {9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), compare_float);
int i = 0;
for (i = 0; i < sz; i++) {
printf("%f ", arr[i]);
}
printf("\n");
}
int compare_Stu_by_age(const void *elem1, const void *elem2){
return (int )(((Stu*)elem1)->age - ((Stu*)elem2)->age);
}
int compare_Stu_by_name(const void *elem1, const void *elem2){
return strcmp(((Stu*)elem1)->name, ((Stu*)elem2)->name);
}
void test3() {
Stu arr[3] = {{"zhangsan", 20},
{"lisi", 30},
{"wangwu", 18}};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), compare_Stu_by_age);
int i = 0;
for (i = 0; i < sz; i++) {
printf("%s ", arr[i].name);
printf("%d ", arr[i].age);
}
printf("\n");
qsort(arr, sz, sizeof(arr[0]), compare_Stu_by_name);
i = 0;
for (i = 0; i < sz; i++) {
printf("%s ", arr[i].name);
printf("%d ", arr[i].age);
}
printf("\n");
}
int main() {
test1();
test2();
test3();
return 0;
}
qsort(参数1,参数2,参数3,参数4); 第一个参数:待排序数组的首元素地址 第二个参数:待排序数组的元素大小 第三个参数:待排序数组的每个元素的大小-单位是字节 第四个参数:是函数指针,比较两个元素的所用函数的地址-这个函数使用者自己实现。 函数指针的两个参数是:待比较的两个元素的地址。函数返回值为int类型。
我们发现库函数中的qsort函数可以实现除了整型之外的几乎所有类型的排序,我们的冒牌排序是不是也可以设计成这样呢?
首先我们来考虑函数的参数。1.需要知道首元素的地址(从哪里开始的),2.整个数组的大小(来判断需要几轮,每轮运算多少。)3.运算的是什么类型的,但是类型是不能传参的,所以我们可以传这个类型的大小(宽度)。4.两个值进行比较的函数地址。
主要功能实现
void Swap(char *buf1, char *buf2, int width) {
int i = 0;
for (i = 0; i < width; i++) {
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void *base, int sz, int width, int (*compare)(const void *elem1, const void *elem2)) {
for (int i = 0; i < sz - 1; i++) {
int isSort = 1;
for (int j = 0; j < sz - i - 1; j++) {
if (compare((char *) base + j * width, (char *) base + (j + 1) * width) > 0) {
Swap((char *) base + j * width, (char *) base + (j + 1) * width, width);
isSort = 0;
}
}
if (isSort == 1) break;
}
}
解析:整体使用的是冒泡排序的思想。难点:本人认为共有两个难点:1.在冒泡排序进行两个元素判断的时候,因为传进来的是void类型的指针是不能直接使用的。但是我们有被比较变量的宽度,通过char指针控制步长可以精确匹配到首地址然后进行量元素的比较。(在这里本来有个疑问,访问到变量的首地址,此时还是char怎么可以得到变量的全部部分呢?答案是这里是个回调函数,回调到用户编写的函数后又进行了一次强制类型转化,这时我们就访问到了变量的全部。)比较完之后就要进行两值的交换。2.两值的交换就是第二个问题了:步长就相当于两个变量的间隔,只要通过这个间隔从左开始,向右逐个交换。就可以将两个变量所在的地址中的内容全部交换。(下面是我的图解)。
完整代码展示
#include <string.h>
void bubble_sort(void *base, int sz, int width, int (*compare)(const void *elem1, const void *elem2));
typedef struct Stu {
char name[20];
int age;
} Stu;
int compare_int(const void *elem1, const void *elem2) {
return *(int *) elem1 - *(int *) elem2;
}
void test1() {
int arr[10] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), compare_int);
int i = 0;
for (i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int compare_float(const void *elem1, const void *elem2) {
return (int) (*(float *) elem1 - *(float *) elem2);
}
void test2() {
float arr[] = {9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0};
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), compare_float);
int i = 0;
for (i = 0; i < sz; i++) {
printf("%f ", arr[i]);
}
printf("\n");
}
int compare_Stu_by_age(const void *elem1, const void *elem2) {
return (int) (((Stu *) elem1)->age - ((Stu *) elem2)->age);
}
int compare_Stu_by_name(const void *elem1, const void *elem2) {
return strcmp(((Stu *) elem1)->name, ((Stu *) elem2)->name);
}
void test3() {
Stu arr[3] = {{"zhangsan", 20},
{"lisi", 30},
{"wangwu", 18}};
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), compare_Stu_by_age);
int i = 0;
for (i = 0; i < sz; i++) {
printf("%s ", arr[i].name);
printf("%d ", arr[i].age);
}
printf("\n");
bubble_sort(arr, sz, sizeof(arr[0]), compare_Stu_by_name);
i = 0;
for (i = 0; i < sz; i++) {
printf("%s ", arr[i].name);
printf("%d ", arr[i].age);
}
printf("\n");
}
void Swap(char *buf1, char *buf2, int width) {
int i = 0;
for (i = 0; i < width; i++) {
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void *base, int sz, int width, int (*compare)(const void *elem1, const void *elem2)) {
for (int i = 0; i < sz - 1; i++) {
int isSort = 1;
for (int j = 0; j < sz - i - 1; j++) {
if (compare((char *) base + j * width, (char *) base + (j + 1) * width) > 0) {
Swap((char *) base + j * width, (char *) base + (j + 1) * width, width);
isSort = 0;
}
}
if (isSort == 1) break;
}
}
int main() {
test1();
test2();
test3();
return 0;
}
指针和数组面试题解析
- 一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));
- 字符数组
(1)直接在数组中初始化存放
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
(2)使用字符串进行初始化
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
- 常量字符串地址传递给指针。
char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));
- 二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));
小结:
1.sizeof(数组名),这里的数组名代表的是整个数组,计算的是数组的大小。 2.&数组名,这个数组名代表的是整个数组,取出的是整个数组的地址 3.除此之外所有的数组名都是首元素的地址。
指针笔试题
笔试题1:程序执行结果是什么?
int main() {
int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int *) (&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
解析:答案是2,5。&a + 1是跳过整个数组的下一个地址。数组指针加一还是数组指针类型,不能存放进整型指针中,所以进行了强制类型转化。在输出的时候,输出的第一个值是数组首元素地址加一也就是第二个元素的地址,然后解引用得到的就是第二个元素,所以第一个输出的就是2;ptr是跳过整个数组的下一个地址,且ptr是int类型的指针,那么指针减一就是他的上一个地址,所以指向的应该是数组a的最后一个元素,所以第二个输出的是5。
笔试题2:
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
解析:0x100014 0x100001 0x100004。首先p是什么,p是结构体指针;0x1就是十进制的1。第一个打印中要跳过这个结构体的下一个地址是什么?0x100000(16)+20(10)=0x100014(16)。第二个打印,先将p转换成无符号长整型然后加一。按照地址打印输出:0x100000(16)+1=0x100001(16)。第三个打印,先将p转换成无符号的int型指针,加一就是跳过一个指针的大小就是0x100000(16)+4(10)=0x100004(16)。
笔试题3:
int main()
{
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf( "%x,%x", ptr1[-1], *ptr2);
return 0;
}
解析:4,2000000。首先解释ptr1[-1],同笔试题1中的内容ptr1是跳过数组后的第一个地址,ptr1[-1]就是这个地址的上一个int。那就是a[3]。对于第二个我们直接图解(黄色是第一步,蓝色第二步,红色第三步)。因为我们的电脑是小端存储模式,所以存储数组具体元素的时候,按照如下的存储。(int *)((int)a + 1)。首先是将a的地址强制转化为int,然后对其加1,再转化为int型的指针,最后就是就是表示红色框框柱的部分。读取这部分,根据小端存储模式:0x02000000。输出时去掉高位的0。
笔试题4:
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
解析:答案是1。(0, 1), (2, 3), (4, 5)先要想到这是逗号表达式,所以int a[3][2] = { (0, 1), (2, 3), (4, 5) };这句话相当于是int a[3][2]={1,3,5}。在内存中存储的时候,第一行第一个是1,第一行第二个是3,第二行第一个是5,其余的全都是0。a[0]是第一行的数组,输出的时候没有sizeof,没有&所以代表的是首元素地址。p[0]相当于*(p+0)就是1。
笔试题5
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
解析:答案是FFFFFFFFFFFFFFFC,-4。int(*p)[4],是指针数组。将a[5][5]强制赋值到p中,结果如图下所示(数组分布)。图中量蓝点的地址进行作差,因为体重是低地址减去高地址,答案是-4。但是用%p的形式输出的时候应该是输出-4对应补码,所对应的16进制数FFFFFFFFFFFFFFFC(32位是FFFFFFFC) , -4。
笔试题6:
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr1 = (int *)(&aa + 1);
int *ptr2 = (int *)(*(aa + 1));
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
解析:答案是10,5。&arr是整个数组,经过计算后的ptr是整个数组后面的第一个地址。在输出的时候int指针-1然后取地址,得到的应该是aa数组的最后一个元素,10。aa + 1是第二行的地址,然后解引用得到的就是第二行,再强制类型转化为指针。输出的时候,ptr2 - 1,地址减一,减去的是int*,此时取到的就是第二行的前一行的最后一个的地址,再解引用,得到的就是该位置的元素为5。
补充:
int arr[] = {1,2,3,4};
int* p = arr;
在这种情况下,*(p+2) <=>p[2]<=>*(arr+2)<=>arr[2]
笔试题7
int main()
{
char* a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
解析:答案是at。a是指针数组。a代表的是首元素的地址,就是第一个字符串"work"的地址。因为a本身就是一个指针,然后将这个指针放在二级指针pa中存储。pa++就相当于与a+1;就是第二个字符串的地址,解引用后就是第二个字符串也就是"at"。
笔试题8
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
return 0;
}
解析:答案是:POINT,ER,ST,。
注意:1.自增之后值就已经改变了。2.取的是字符串的地址还是字符的地址。虽然是一个地址,但是运算不同。
首先char *c[] = {“ENTER”,“NEW”,“POINT”,“FIRST”};是一个指针数组(数组中存放的指针。)。char**cp[] = {c+3,c+2,c+1,c};中c是首元素的地址。这样的话,cp[]就相当于是将数组c的逆序。cp是首元素地址存放到cpp二级指针变量中。
第一个printf中先++cpp,这个指针要加一,那就是cp中的第二个元素的地址,第一次解引用得到是c中第三个元素的地址。第二次解引用就该字符串的第一个字符的地址,输出"%s"得到"POINT"。
第二个printf中根据优先级,先++再解引用再–再解引用再加三。但是,这里在第一次printf的时候我们经cpp指到了cp中的第二个元素的地址。所以1.先++指向的是cp中的第三个元素的地址。再解引用得到的是c中的第二个元素的地址(不懂把第一个printf再看一遍),再–得到的是c中第一个元素的地址,再解引用就是c中第一个字符串的第一个字符的地址。最后加3得到的就是从从第四个字符开始的字符串"ER"。
第三个printf中*cpp[-2]+3等价于*(*(cpp-2))+3,然后+3。首先计算之前强调cpp已经指到了cp的第三个元素,(cpp-2)指到了cp的第一个元素,解引用得到的是c的第四个元素字符串,再解引用,取到的是c中第四个字符串中的第一个字符的地址。输出"%s"得到"ST"。
第四个printf中cpp[-1][-1]+1等价于*(*(cpp-1)-1)+1。因为我们知道cp在第二次printf的时候更新了位置(更新到了cp的第三个元素的位置),但是在第三个printf中并没有作出修改,所以(cpp-1)代表的是cp中第二个元素的位置,解引用访问到的是c中的第三个元素-字符串的地址。在这个基础上先-1取到的就是c中第二个字符串的地址,然后解引用操作,就是c中第二个字符串的第一个字符的地址,再+1,就是c中第二个字符串的第二个字符。输出"%s"得到"EW"。
|