目录???????
1.字符指针
?2.指针数组
?3.数组指针
4.数组参数、指针参数
5.函数指针
?6.函数指针数组
7.指向函数指针数组的指针
8.回调函数
9.例题
10.笔试题
正文:
前面我们已经对指针有了初步了解,此处做简单回顾:
(1)内存会划分为小的内存单元,每个内存单元会有一个编号,这个编号也被称为地址,把存放地址的变量称为指针变量。即:内存编号=地址=指针,它们都是一个数值。我们常说的int* p;把p称为一个指针,其实就是一个指针变量的意思;
(2)指针的大小是固定的4或8个字节(32位或64位平台);
(3)指针是有类型的,指针的类型决定了指针+整数的步长和指针解引用操作时的权限;
(4)指针的运算:指针+-整数,指针-指针,指针的关系运算。
1.字符指针
一般有两种使用方式:
第一种:
#include<stdio.h>
int main()
{
char ch = 'w';
char* pc = &ch;
*pc = 'a';
printf("%c\n", ch);
return 0;
}
第二种:
我们知道在×86环境下,指针变量p是4个字节,故而p中存放的不可能是"abcdef"的字符串。当我们对p进行解引用操作并打印,可以发现输出结果为a,也就是说,指针变量p存放的是该字符串瘦首字符a的地址。如果想打印字符串,则输出语句应改为:printf("%s\n",p);
同时应注意,“abcdef”作为常量字符串,是不能被修改的,如果强行进行类似于*p='w'这种操作,代码将无法运行。为了避免这种情况,我们可以在创建字符指针前进行const修饰,即:
const char* p="abcdef";
然后如果再进行*p='w'强行修改的操作,那么编译器会报错。而且在部分编译器中,如果不加const修饰会报警告;
练习:
?2.指针数组
顾名思义,指针数组就是存放指针的数组
int* arr[10]; //存放整型指针的数组
char* ch[5]; //存放字符指针的数组
应用示例:
?但是在实际应用中,很少会将元素地址刻意存放到一个数组中。此处只是作为示例。
?更常用的如下例所示:
?3.数组指针
3.1 定义
首先明确数组指针是一个指针。
int a=10;
int* p=&a;
//整形指针是指向整型的指针,用于存放整型变量地址
char ch='w';
char* pc=&ch;
//字符指针是指向字符的指针,用于存放字符变量地址
?故而我们可以类推,数组指针即是指向数组的指针。
//试区分哪一个是数组指针
int* p1[10];
int(*p2)[10];
?
3.2 区别&数组名(&arr)与数组名(arr)的区别
再举一例:?
char* arr[5];
p=&arr;
//∴char (*p)[5]=&arr
再次回顾数组名的含义:
通常情况下数组名都表示首元素地址,两个例外:①sizeof(数组名)计算的是整个数组的大小;②&(数组名)取出的是整个数组的地址;
3.3 数组指针的使用
首先看一组一维数组的示例:
现要求设计代码打印一个一维数组的元素:
法一:形参为指针(也可做形参为数组,比较简单,此处不做演示)
#include <stdio.h>
void print1(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(arr + i));
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
print1(arr, sz);
return 0;
}
?这也是我们常用的方法;
法二:使用数组指针:
#include <stdio.h>
void print1(int(*p)[10], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *((*p) + i));
//*p相当于数组名
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
print1(&arr, sz);
return 0;
}
也可以实现要求,但是会发现,步骤繁琐并且大可不必使用数组指针。
通常在使用一维数组时,我们很少会使用数组指针。
接下来看一组二维数组的示例:
要求设计代码打印一个二维数组的元素:
法一:形参为数组
#include<stdio.h>
void print2(int arr[3][5], int c, int r)
{
int i = 0;
for (i = 0; i < c; i++)
{
int j = 0;
for (int j = 0; j < r; j++)
{
printf("%d ", arr[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} };
print2(arr, 3, 5);
return 0;
}
这也是我们常用的方法;
法二:形参为指针:
#include<stdio.h>
void print2(int(*p)[5], int c, int r)
{
int i = 0;
for (i = 0; i < c; i++)
{
int j = 0;
for (int j = 0; j < r; j++)
{
printf("%d ", *(*(p+i)+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} };
print2(arr, 3, 5);
return 0;
}
解析:
?稍作总结: ?
int arr[5];
//arr是一个整型数组,每个元素是int型的
int* parr1[10];
//parr1是一个10个整型元素的指针数组
int(*parr2)[10];
//parr2是一个数组指针,每一个元素的类型是int
int(*parr3[10])[5];
//parr3是一个数组,有十个元素,每个元素的类型是int(*)[5];、
//每一个元素是一个数字指针∴parr3是一个存放数组指针的数组
parr3图解:
void test1(int(*p)[5])
{ }
void test2(int(*p)[3][5])
{ }
int main()
{
int arr[3][5];
test1(arr);
test2(&arr);
return 0;
}
4.数组参数、指针参数
4.1 一维数组传参
4.2 二维数组传参?
4.3 一级指针传参?
#include<stdio.h>
void test(int* ptr) //当形参为一个指针时
{
//...
}
int main()
{
int a = 10;
int* p = &a;
int arr[10];
test(arr); //数组
test(p); //指针
test(&a);
return 0;
}
4.4 二级指针传参
#include<stdio.h>
void test(char** ppc)
{
//...
}
int main()
{
char a = 'w';
char* pa = &a;
char**ppa=&pa;
test(ppa); //ppa是一个二级指针
test(&pa);
char* arr[4];
test(arr); //指针数组
return 0;
}
5.函数指针
5.1 函数指针的定义
函数指针即指向函数的指针
#include<stdio.h>
int add(int x, int y)
{
return x + y;
}
int main()
{
int arr[10];
int(*p)[10]=&arr; //p是一个数组指针变量
printf("%p\n", &add);
printf("%p\n", add); //两种表示都是函数的地址
int(*pf)(int,int)=add; //pf就是函数指针
//(指向函数的返回类型)(*指针名)(指向函数的参数类型)
return 0;
}
运行结果如下:
?表示函数地址时,函数名等同于&函数名,二者表示形式不同但意义完全相同。
同时请注意,当我们去除函数指针名就可以得到函数指针类型,即int (*) (int ,int);
5.2 函数指针的使用
#include<stdio.h>
int add(int x, int y)
{
return x + y;
}
int main()
{
int(*pf)(int,int)=add;
int ret=(*pf)(2, 3);
printf("%d\n", ret);
int ret2 = add(2, 3);
printf("%d\n", ret2);
int ret3 = pf(2, 3);
printf("%d\n", ret3);
return 0;
}
运行结果如下:
? ? ?可以发现其实pf与*pf完全等效
一个有趣的例子:
?6.函数指针数组
函数指针数组即存放函数地址的数组,表达为:
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;
}
#include<stdio.h>
int main()
{
int(*pf1)(int, int) = add;
int(*pf2)(int, int) = sub;
int(*pf3)(int, int) = mul;
int(*pf4)(int, int) = div;
//创建一个函数指针数组
int(*pf[4])(int, int) = { add,sub,mul,div };
int i = 0;
for (i = 0; i < 4; i++)
{
int ret = pf[i](8, 2);
printf("%d ", ret);
}
return 0;
}
当函数返回类型、函数参数类型、函数参数数量都相同时,我们可以利用函数指针数组进行合并,简化代码,以简单实现计算器部分功能为例:
#include<stdio.h>
void menu()
{
printf("*********************\n");
printf("********1.Add********\n");
printf("********2.Sub********\n");
printf("********3.Mul********\n");
printf("********4.Div********\n");
printf("********5.Exit*******\n");
printf("*********************\n");
}
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 input = 0;
int x = 0;
int y = 0;
int ret = 0;
int(*Arr[])(int, int) = { NULL,Add,Sub,Mul,Div };
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
}
else if (input >= 1 && input <= 4)
{
printf("请输入2个操作数:>");
scanf("%d%d", &x, & y);
ret = Arr[input](x, y);
printf("ret=%d\n", ret);
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
7.指向函数指针数组的指针
#include<stdio.h>
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 arr1[10] = { 1,2,3,4,5,6,7 };
int* arr2[5];
//数组指针
int(*p1)[10] = &arr1;
int*(*p2)[5] = &arr2;
//函数指针
int(*pf)(int,int) = &Add;
//函数指针数组
int(*pfArr[4])(int,int);
//p3是指向函数指针数组的指针
int(*(*p3)[4])(int,int) = &pfArr;
return 0;
}
图示:
函数调用:
#include<stdio.h>
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(*pfArr[4])(int, int) = { Add,Sub,Mul,Div };
//p3是指向函数指针数组的指针
int(*(*p3)[4])(int,int) = &pfArr;
//调用函数
int i = 0;
for (i = 0; i < 4; i++)
{
int ret = (*p3)[i](16, 4);
printf("%d\n", ret);
}
return 0;
}
8.回调函数
(1)定义:回调函数就是一个通过函数指针调用的函数。如果将一个函数的地址作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,就称为回调函数。糊掉函数不是由该函数的实现方直接调用,而是在特定的事件或者条件发生时由另一方调用的,用于对该事件或条件进行响应。
以简单实现计算器部分功能为例:
常用的switch、case语句的实现方式如下:
#include<stdio.h>
void menu()
{
printf("*********************\n");
printf("********1.Add********\n");
printf("********2.Sub********\n");
printf("********3.Mul********\n");
printf("********4.Div********\n");
printf("********5.Exit*******\n");
printf("*********************\n");
}
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 input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
ret = Add(x, y);
printf("ret=%d\n", ret);
break;
case 2:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
ret = Sub(x, y);
printf("ret=%d\n", ret);
break;
case 3:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
ret = Mul(x, y);
printf("ret=%d\n", ret);
break;
case 4:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
ret = Div(x, y);
printf("ret=%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误\n");
}
} while (input);
return 0;
}
但是我们会发现在case 1至case 4语句部分代码存在大量重复,这时候我们可以分装函数,将Add,Sub,Mul,Div函数设计为回调函数,进行代码简化:
#include<stdio.h>
void menu()
{
printf("*********************\n");
printf("********1.Add********\n");
printf("********2.Sub********\n");
printf("********3.Mul********\n");
printf("********4.Div********\n");
printf("********5.Exit*******\n");
printf("*********************\n");
}
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;
}
void calc(int(*pf)(int, int))
{
int x = 0;
int y = 0;
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
pf(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
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");
}
} while (input);
return 0;
}
(2)qsort函数:
qsort是一个库函数,是基于快速排序算法实现的一个排序函数,可以排序任意类型的数据。
?ps:(1)比较函数要求编写者自定义是因为,并非所有数据都像整型数据一样可以直接用>或<进行排序,需要我们根据实际情况提供一个函数,实现两个函数的比较;
(2)void* 类型的指针可以接收任何类型的地址,但是该种类型的指针如果直接解引用不能确定操作的权限,故而不能直接进行解引用操做;
我们可以将bubble_sort(冒泡排序)根据qsort函数逻辑进行改进,使其基于冒泡排序可以对任意类型数据都可以进行排序:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void print_arr(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
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 num,int width,void*(*cmp)(const void* e1,const void* e2))
{
int i = 0;
for (i = 0; i < num-1; i++)
{
int j = 0;
for(j = 0; j <num-1-i ; j++)//比较
{
//交换
if (cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
}
}
}
}
int cmp_int(const void* e1, const void* e2)
//无确切类型的指针不能直接进行解引用操做
{
//表达方式一:
/*if (*(int*)e1 > *(int*)e2)
return 1;
else if (*(int*)e1 == *(int*)e2)
return 0;
else
return -1;*/
//表达方式二:
return *(int*)e1 - *(int*)e2;
}
//冒泡排序
void test1()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
//改为排序为升序
bubble_sort(arr, sz,sizeof(arr[0]),cmp_int);
print_arr(arr, sz);
}
//qsort函数排序整型数据
void test2()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
//改为排序为升序
qsort(arr,sz,sizeof(arr[0]),cmp_int);
print_arr(arr, sz);
}
struct Stu
{
char name[20];
int age;
double score;
};
//int cmp_stu_bu_age(const void* e1, const void* e2)
//{
// return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
//}
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//排序结构体
void test3()
{
struct Stu arr[3] = { {"zhangsan",20,55.5},{"lisi",30,88.0},{"wangwu",10,90.0}};
int sz = sizeof(arr) / sizeof(arr[0]);
//qsort(arr, sz, sizeof(arr[0]),cmp_stu_bu_age);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
test1();
printf("\n");
test2();
printf("\n");
test3();
return 0;
}
9.例题
1.一维数组:
//sizeof计算的是对象所占内存的大小
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a)); //4×4=16
//sizeof(数组名)表示整个数组的大小
printf("%d\n", sizeof(a + 0)); //4 or 8
//不属于特例的数组名出现表示数组首元素地址,a+0仍表示首元素地址
//地址的大小在32位平台(×86)上是4个字节,在64位平台(×64)上是8个字节
printf("%d\n", sizeof(*a)); //4
//sizeof(int型数据)
printf("%d\n", sizeof(a + 1)); //4 or 8
//数组首元素地址+1表示数组第二个元素的地址
printf("%d\n", sizeof(a[1])); //4
//数组第一个元素的大小,数组元素数据类型为int
printf("%d\n", sizeof(&a)); //4
//&数组名表示数组的地址,地址大小只能为4或8个字节
printf("%d\n", sizeof(*&a)); //16
//对数组的地址进行解引用得到的是整个数组,大小为4×4
printf("%d\n", sizeof(&a + 1)); //4 or 8
//对数组的地址+1跳过了整个数组,指向数组末元素的后方,仍然是一个地址
printf("%d\n", sizeof(&a[0]));4 or 8
//数组首元素的地址
printf("%d\n", sizeof(&a[0] + 1)); // 4 or 8
//数组第二个元素的地址
注:数组名在一般情况下都表示数组首元素地址,但是有两个特例:
① sizeof(数组名)表示数组的大小,单位为字节;
② &数组名表示整个数组的地址;
2.字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr)); //6
//sizeof(数组名)为数组大小,单位为字节
printf("%d\n", sizeof(arr+0));//4 or 8
//数组名不属于特例,arr表示首元素地址,+0后仍为首元素地址
printf("%d\n", sizeof(*arr));//1
//对数组首元素地址解引用,得到数组首元素,char类型数据大小为1字节
printf("%d\n", sizeof(arr[1]));//1
//arr[1]表示数组第二个元素,char类型数据大小为1个字节
printf("%d\n", sizeof(&arr));//4 or 8
//&数组名为整个数组地址,整个数组地址的大小为4或8个字节
printf("%d\n", sizeof(&arr+1));//4 or 8
//整个数组的地址+1仍然得到一个地址,该地址是从数组地址开始向后跳过了整个数组产生的一个地址
printf("%d\n", sizeof(&arr[0]+1));//4 or 8
//数组第二个元素地址的大小
printf("%d\n", strlen(arr)); //大于6的随机值
//strlen函数以\0作为结束标志,arr数组中没有\0,strlen函数会继续向后找\0,f后内存空间未知
printf("%d\n", strlen(arr + 0)); //大于6的随机值
//arr+0仍然表示数组首元素地址,逻辑同上
printf("%d\n", strlen(*arr)); //本质是错误的,非法访问
//*arr表示数组首元素,即'a',其ASCII码值为97,没有传递给strlen一个地址,本质上是错误的代码
printf("%d\n", strlen(arr[1])); //本质是错误的,非法访问
//同上,将b元素的ASCII码值传递给了strlen
printf("%d\n", strlen(&arr)); //大于6的随机值
//将整个数组的地址传递给strlen函数,数组中不含有\0
printf("%d\n", strlen(&arr + 1)); //随机值
同上,从数组最后一个元素结束开始向后寻找\0
printf("%d\n", strlen(&arr[0] + 1)); //大于5的随机值
//&arr[0]+1表示数组第二个元素从a元素开始向后寻找\0
3.字符串
char arr[] = "abcdef";
printf("%d\n", sizeof(arr)); //7
//字符串默认结尾为\0,而sizeof(数组名)计算的是整个数组的大小,
printf("%d\n", sizeof(arr+0)); //4 or 8
//数组名表示数组首元素地址,即sizeof(地址)
printf("%d\n", sizeof(*arr)); //1
//*arr表示数组首元素,char类型数据大小为1个字节
printf("%d\n", sizeof(arr[1])); //1
//数组第二个元素的大小
printf("%d\n", sizeof(&arr));//4 or 8
//数组的地址仍然为一个地址
printf("%d\n", sizeof(&arr+1)); //4 or 8
//数组的地址+1后仍然为一个地址
printf("%d\n", sizeof(&arr[0]+1));//4 or 8
//&arr[0]+1表示数组第二个元素的地址
printf("%d\n", strlen(arr)); //6
//strlen函数统计的是\0前的字符个数
printf("%d\n", strlen(arr+0)); //6
printf("%d\n", strlen(*arr)); //写法错误
//*arr表示数组第一个元素,会造成非法访问
printf("%d\n", strlen(arr[1])); //写法错误
//同上
printf("%d\n", strlen(&arr)); //6
//strlen函数只要接收一个地址就会从该地址开始向后寻找\0并统计数目
printf("%d\n", strlen(&arr+1)); //随机值
//数组地址+1就是将\0后的地址交给了strlen函数,后面内存未知
printf("%d\n", strlen(&arr[0]+1)); //5
//&arr[0]+1表示数组第二个元素,至\0有5个元素
关于指针:?
char* p = "abcdef";
printf("%d\n", sizeof(p)); //4 or 8
//p存放的是a的地址,此处计算的是指针变量的大小
printf("%d\n", sizeof(p+1)); // 4 or 8
//p+1表示的是b的地址
printf("%d\n", sizeof(*p)); // 1
//*p表示的是字符a
printf("%d\n", sizeof(p[0])); //1
//p[0]等价于*(p+0)等价于*p等价于字符a
printf("%d\n", sizeof(&p)); // 4 or 8
//p表示的是a的地址,&p存放p的地址,仍然为一个地址
printf("%d\n", sizeof(&p+1)); // 4 or 8
//&p+1仍然是一个地址
printf("%d\n", sizeof(&p[0]+1)); //4 or 8
//&p[0]表示字符a的地址,+1后表示字符b的地址
printf("%d\n", strlen(p)); //6
//p等同于a的地址
printf("%d\n", strlen(p+1)); //5
//p+1等同于b的地址
printf("%d\n", strlen(*p)); // 错误
//将a的值传递给了strlen
printf("%d\n", strlen(p[0])); //错误
//同上
printf("%d\n", strlen(&p)); //随机值
//存放a的地址的地址后的内存未知
printf("%d\n", strlen(&p+1)); //随机值
//同上
printf("%d\n", strlen(&p[0]+1)); //5
//&p[0]+1等同于字符b
4.二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));//48
//sizeof(数组名)表示整个数组的大小,该数组共12个int型数据
printf("%d\n",sizeof(a[0][0]));//4
//a[0][0]表示数组第一行第一列元素大小
printf("%d\n",sizeof(a[0]));//16
//a[0]表示二维数组第一行元素,共4个int型数据,共占16个字节
printf("%d\n",sizeof(a[0]+1));//4 or 8
//a[0]表示第一行元素的数组名,也是a[0][0]的地址,+1后表示第一行第二个元素的地址
printf("%d\n",sizeof(*(a[0]+1)));//4
//a[0]+1表示a[0][0]的地址,解引用得到a[0][1]
printf("%d\n",sizeof(a+1));// 4 or 8
//a表示数组第一行元素的地址,+1跳过第一行数组,表示数组第二行元素的地址
printf("%d\n",sizeof(*(a+1)));//16
//a+1表示数组第二行元素的地址,解引用得到第二行元素,共4个整型数据
printf("%d\n",sizeof(&a[0]+1));// 4 or 8
//第一行数组的地址+1得到第二行数组的地址
printf("%d\n",sizeof(*(&a[0]+1)));//16
//解引用得到第二行数组
printf("%d\n",sizeof(*a));//16
//a表示第一行元素的地址,解引用得到数组第一行元素共4个int型数据
printf("%d\n",sizeof(a[3]));//16
//a[3]表示数组第四行的元素,内存不会真的访问,识别类型后直接得到大小,为16个字节
ps:
(1)sizeof是一个单目操作符,计算的是对象所占内存的大小,单位是字节,返回类型是size_t,它不在乎内存中存放的是什么,只在乎内存的大小;
(2)strlen是一个库函数,专用于计算字符串的长度,从给定的地址向后访问字符,统计该地址到\0之前的字符数;
(3)在理解二维数组时,可以先将二维数组想象为一维数组,将二维数组的每一行看作一个元素,比如二维数组arr[3][4],arr[0]就表示第二行元素的数组名。
10.笔试题
(1)
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
//&a取出挣个数组的地址,类型为数组指针,int(*)[5],故而强制类型转换为int*
printf("%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
输出结果为2,5;
(2)
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}* p;
//已知,结构体Test类型的变量大小是20个字节
int main()
{
p=(struct Test*) 0x100000;
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
//将p转化为无符号长整型,+1即+一个字节
printf("%p\n", (unsigned int*)p + 0x1);
return 0; }
输出结果为00100014 00100001?00100004;
%p打印的是地址,十六进制需要展现32位,即4个字节;
(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
图示:
?(4)
#include <stdio.h>
int main()
{
? ?int a[3][2] = { (0, 1), (2, 3), (4, 5) };
//注意区别与int a[3][2]={{0,1},{2,3},{4,5}};的区别
//{ (0, 1), (2, 3), (4, 5) }是逗号表达式,相当于{1,3,5}
//这个二维数组是:{{1,3},{5,0},{0,0}}
? ?int *p;
? ?p = a[0];
? ?printf( "%d", p[0]);
return 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;
}
?输出结果为fffffc -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
注解:在int *ptr2 = (int *)(*(aa + 1));中,aa表示数组第一行元素的地址,+1后表示数组第二行元素的地址,解引用得到了数组第二行元素,也就是数组第二行元素的数组名,同时也表示第二行数组首元素6的地址,再进行ptr2-1得到元素5的地址,解引用得到元素5;
(7)
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
输出结果为
注解:a是一个指针数组,其元素为三个字符串首字符的地址。再将a的首元素的地址赋值给二级指针变量pa,pa++后得到数组第二个元素"at"的地址,再解引用得到字符串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; }
图示:
?注解:① ++cpp指向c+2,解引用得到c+2,再解引用得到其指向的内容P的地址,%s形式打印得到字符串POINT;
② ++前置会改变原本的值,第二句输出建立在第一次输出的基础上,++cpp指向c+1,解引用得到c+1,再--得到c,此步骤将原本c+1指向的空间改为c指向的空间,再解引用得到E的地址,+3指向E,打印结果为ER;
③ cpp经过以上两个步骤已经指向c+1,cpp[-2]等价于*(cpp-2),即第三句输出的是**(cpp-2)+3,cpp-2指向c=3,解引用得到c+3,再解引用得到F的地址,+3得到S的地址,打印结果为ST;
④ cpp[-1][即*(cpp-1),cpp[-1][-1]等价于*(*(cpp-1)-1),即第三句输出的是*(*(cpp-1)-1)+1,注意上一句输出结果并未改变cpp的指向,cpp仍然指向c+1,-1指向c+2,解引用得到c2,再-1得到c+1,指向N的地址,再解引用得到N,再+1得到E,打印结果为EW;
||终
|