目录
前言
指针的主题,我们在初级阶段的《指针》章节已经接触过了,我们知道了指针的概念: 1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。 2. 指针的大小是固定的 4/8 个字节( 32 位平台 /64 位平台)。 3. 指针是有类型,指针的类型决定了指针的 +- 整数的步长,指针解引用操作的时候的权限。 4. 指针的运算。 这个章节,我们继续探讨指针的高级主题。
一.字符指针
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
char ch = 'q';
char * pc = &ch;
char* ps = "hello bit";
char arr[] = "hello bit";
*ps = 'w';//err
arr[0] = 'w';
printf("%c\n", *ps);//h
printf("%s\n", ps);//hello bit
printf("%s\n", arr);//wello bit
return 0;
}
- char* ps = "hello bit";不是把字符串 hello bit放到字符指针 pstr 里了,而是把"hello bit"这个字符串的首字符的地址存储在了ps中
- "hello bit"是一个常量字符串,所以不能被修改, 则*ps = 'w';这个语句就是错的
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char* str3 = "hello bit.";
const char* str4 = "hello bit.";
//*str3 = 'w';
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if (str3 == str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
str1和str2不同,str3和str4相同
。
- 这其实也很好理解"hello bit.",这是一个常量字符串,不能被修改,又因为str1和str2都是指向同一个常量字符串,自然也就不需要再开辟一段空间放相同的常量字符串
- srt1和str2虽然数组的内容一样,但是str1和str2中的"hello bit."是可以被修改,所以开辟了2个不同数组存放"hello bit."
二.指针数组
什么是指针数组,简单的说就是一个数组,它的元素是指针,比如
int* arr1 [ 10 ]; // 整形指针的数组 char * arr2 [ 4 ]; // 一级字符指针的数组 char ** arr3 [ 5 ]; // 二级字符指针的数组
看代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = 30;
int* arr[3] = {&a, &b, &c};
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", *(arr[i]));
}
return 0;
}
- 上面的这段代码是使用了指针数组的,但是使用了之后,感觉很别扭,
看代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a[5] = { 1,2,3,4,5 };
int b[] = { 2,3,4,5,6 };
int c[] = { 3,4,5,6,7 };
int* arr[3] = { a,b,c };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
//printf("%d ", *(arr[i] + j));
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
- 这样使用指针数组,是不是感觉像是二维数组的访问
三.数组指针
什么是数组指针呢?简单的说就是一个指针,它指向的是一个数组,比如
int ( * p2 )[ 10 ];
- 这里要注意:[]的优先级比*的优先级更高,如果写成 int *p2[10];则p2就变成了指针数组,所以我这里加了一个()
看代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 10;
int*pa = &a;
char ch = 'w';
char*pc = &ch;
double* d[5];
double* (*pd)[5] = &d;//ok pd就是一个数组指针
int arr[10] = {1,2,3,4,5};
int (*parr)[10] = &arr;//取出的是数组的地址
//parr就是一个数组指针 - 其中存放的是数组的地址
//arr - 数组名是首元素的地址 - arr[0]的地址
return 0;
}
- 上面对pd和parr就是数组指针,一种指针,指向数组
看代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[10] = {0};
int* p1 = arr;
int (*p2)[10] = &arr;
printf("%p\n", p1);
printf("%p\n", p1+1);
printf("%p\n", p2);
printf("%p\n", p2+1);
return 0;
}
- 上面开始我就说了,指针加1,是由指针类型决定的,
- p1是一个整形指针,加1跳过一个整形,即4个字节
- p2是一个数组指针,加1跳过一个数组,即40个字节
&数组名vs数组名
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[10] = {0};
printf("%p\n", arr);
printf("%p\n", arr+1);
printf("%p\n", &arr);
printf("%p\n", &arr+1);
return 0;
}
- 数组名是数组首元素的地址
- 但是有2个例外:
1. sizeof(数组名) - 数组名表示整个数组,计算的是整个数组大小,单位是字节
2. &数组名 - 数组名表示整个数组,取出的是整个数组的地址
数组指针的使用
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void print1(int arr[3][5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
printf("\n");
}
//p是一个数组指针
void print2(int(*p)[5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
printf("\n");
}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7 } };
print1(arr, 3, 5);
print2(arr, 3, 5);//arr数组名,表示数组首元素的地址
return 0;
}
- 数组指针一般的应用场景都是在二维数组中,用在一维数组中感觉很别扭
- 至于为什么能在二维数组中用,主要还是因为二维数组的数组名,相当于一维数组,这里我就不详细的说了,
四.数组参数,指针参数
在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?一维数组传参
- 第一个test正确,传入数组,然后用数组接收,没问题
- 第二个test正确,形参int arr[10]中的10可写,可不写,没问题
- 第三个test正确,虽然传入的是数组,但是数组名是首元素的地址,用int *arr接收没有问题
- 第一个test2正确,传入数组,然后用数组接收,没问题
- 第二个test2正确,虽然传入的是数组,但是数组名是首元素的地址-->int *,用int **arr接收没有问题
二维数组传参
- 第一个test正确,传入数组,然后用数组接收,没问题
- 第二个test错误,传入数组,然后用数组接收,但是形参int arr[][]不能省略列
- 第三个test正确,传入数组,然后用数组接收,形参int arr[][5]可以省略行
- 第四个test错误,虽然传入的是数组,但是数组名是首元素的地址,这个数组是二维数组,所以它的数组名是一维数组,用int* arr接收错误
- 第五个test错误,虽然传入的是数组,但是数组名是首元素的地址,这个数组是二维数组,所以它的数组名是一维数组,用int* arr[5]接收错误,这个东西是指针数组,即不是用正确的数组接收,也不是用正确的指针接收,
- 第六个test正确,虽然传入的是数组,但是数组名是首元素的地址,这个数组是二维数组,所以它的数组名是一维数组,用int (*arr)[5]接收,这个东西是数组指针没有问题
- 第七个test错误,形参是int **arr,只有传过去是一级整形指针,或int *的地址,才能用int **arr接收,显然这里什么也不是
一级指针传参
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void print(int* p, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d\n", *(p + i));
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
比如:
void test1 ( int * p ) {} //test1 函数能接收什么参数? void test2 ( char* p ) {} //test2 函数能接收什么参数?
- 对于test1,int a = 0;可以传入a的地址,即test1(&a),int *pr = &a,可以传入pr指针,即test(pr),test2也同理,
二级指针传参
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);
test(&p);
return 0;
}
思考:
当函数的参数为二级指针的时候,可以接收什么参数?
五.函数指针
什么是函数指针?就是一个指针,它指向函数,
首先看一段代码:函数指针 - 存放函数地址的指针
从上面的代码中我们可以得到一个很重要的结论,函数名==&函数名,顺便一提数组名!=&数组名,
看代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
//int (*pf)(int, int) = &Add;//OK
int (*pf)(int, int) = Add;//Add === pf
int ret = 0;
ret = (*pf)(3, 5);//1
printf("%d\n", ret);
ret = pf(3, 5);//2
printf("%d\n", ret);
ret = Add(3, 5);//3
printf("%d\n", ret);
//int ret = * pf(3, 5);//err
return 0;
}
- pf就是一个函数指针变量
- 这串代码,也从侧面证明出了对于一个函数指针,使用它的*操作符看上去没用
// 代码 1 ( * ( void ( * )()) 0 )(); // 代码 2 void ( * signal ( int , void ( * )( int )))( int );
- 调用0地址处的函数,该函数无参,返回类型是void
- 1. void(*)() - 函数指针类型
- 2. (void(*)())0 - 对0进行强制类型转换,被解释为一个函数地址
- 3. *(void(*)())0 - 对0地址进行了解引用操作
- 4. (*(void(*)())0)() - 调用0地址处的函数
- signal 和()先结合,说明signal是函数名
- signal函数的第一个参数的类型是int,第二个参数的类型是函数指针,该函数指针,指向一个参数为int,返回类型是void的函数
- signal函数的返回类型也是一个函数指针,该函数指针,指向一个参数为int,返回类型是void的函数
- signal是一个函数的声明,
这里可以用
typedef void(*pfun_t)(int) ;//对void(*)(int)的函数指针类型重命名为pfun_t
代码二的简化:pfun_t signal(int, pfun_t);
六.函数指针数组
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:int * arr [ 10 ]; // 数组的每个元素是 int*
什么是函数指针数组?简单的说就是,一个数组,它的元素就是函数指针
比如:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int main()
{
int (*pf1)(int, int) = Add;
int (*pf2)(int, int) = Sub;
int (*pfArr[2])(int, int) = {Add, Sub};//pfArr就是函数指针数组
int ret = pfArr[0](2, 4);//6
printf("%d",ret);
return 0;
}
- pfArr就是函数指针数组,数组的元素是函数的地址,用函数指针指向
- pfArr[0]就是找到下标为0的元素,函数指针的解引用*操作符可以不写
- int (*pfArr[2])(int, int) = {Add, Sub};叫做初始化
- pfArr[0](2, 4);叫做调用
实现一个简单的计算器
加法,减法,乘法,除法
一般写法
#define _CRT_SECURE_NO_WARNINGS 1
#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;
}
void menu()
{
printf("**************************\n");
printf("**** 1. add 2. sub ****\n");
printf("**** 3. mul 4. div ****\n");
printf("**** 0. exit ****\n");
printf("**************************\n");
}
int main()
{
int input = 0;
//计算器-计算整型变量的加、减、乘、除
//a&b a^b a|b a>>b a<<b a>b
do {
menu();
int x = 0;
int y = 0;
int ret = 0;
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入2个操作数>:");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("请输入2个操作数>:");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("请输入2个操作数>:");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("请输入2个操作数>:");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误,重新选择!\n");
break;
}
} while (input);
return 0;
}
- 这样的写法是不是感觉不够美观,代码写的很冗余
函数指针数组的写法
#define _CRT_SECURE_NO_WARNINGS 1
#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;
}
void menu()
{
printf("**************************\n");
printf("**** 1. add 2. sub ****\n");
printf("**** 3. mul 4. div ****\n");
printf("**** 0. exit ****\n");
printf("**************************\n");
}
int main()
{
int input = 0;
//计算器-计算整型变量的加、减、乘、除
//a&b a^b a|b a>>b a<<b a>b
do {
menu();
int (*pfArr[5])(int, int) = { NULL, Add, Sub, Mul, Div };
int x = 0;
int y = 0;
int ret = 0;
printf("请选择:>");
scanf("%d", &input);//2
if (input >= 1 && input <= 4)
{
printf("请输入2个操作数>:");
scanf("%d %d", &x, &y);
ret = (pfArr[input])(x, y);
//ret = pfArr[input](x, y);OK
printf("ret = %d\n", ret);
}
else if(input == 0)
{
printf("退出程序\n");
break;
}
else
{
printf("选择错误\n");
}
} while (input);//只有输入0才退出
return 0;
}
- 这样的写法代码看上去会不会简明一点呢?
- 函数指针数组的用途: 转移表 ,也有一点点像跳板,
- 其中加减乘除的函数返回值的类型需要一样,
七.指向函数指针数组的指针
指向函数指针数组的指针是一个 指针 指针指向一个 数组 ,数组的元素都是 函数指针; 如何定义?#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
八.回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
- 说人话就是一个A函数的地址传给B函数,则B函数就是用函数指针接收,同理C,D,E,F函数的地址也可以传给B函数,
- B函数,这一个函数就可以解引用,调用多个函数,
回调函数实现计算器
#define _CRT_SECURE_NO_WARNINGS 1
#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;
}
void menu()
{
printf("**************************\n");
printf("**** 1. add 2. sub ****\n");
printf("**** 3. mul 4. div ****\n");
printf("**** 0. exit ****\n");
printf("**************************\n");
}
int Calc(int (*pf)(int, int))
{
int x = 0;
int y = 0;
printf("请输入2个操作数>:");
scanf("%d %d", &x, &y);
return pf(x, y);
}
int main()
{
int input = 0;
//计算器-计算整型变量的加、减、乘、除
//a&b a^b a|b a>>b a<<b a>b
do {
menu();
int ret = 0;
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
ret = Calc(Add);
printf("ret = %d\n", ret);
break;
case 2:
ret = Calc(Sub);
printf("ret = %d\n", ret);
break;
case 3:
ret = Calc(Mul);//
printf("ret = %d\n", ret);
break;
case 4:
ret = Calc(Div);//
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误,重新选择!\n");
break;
}
} while (input);
return 0;
}
- Clac这一个函数就能调用多个函数,减少了代码的冗余,Clac就像一个集成器,
首先演示一下qsort函数的使用:
- 不难发现通过,qsort函数之后,使得arr变成了升序,
- 其中qsort不知道使用者的比较方法是什么,所以这里的使用方法是需要自己设计,
- 还有qsort不知道使用者排序的类型是什么,所以比较方法中的形参需要设计成void*
- void*的指针能接受任意类型的指针,
模拟实现qsort函数
其中这段代码有两个灵魂设计点
第一个就是:传入函数参数用void*,然后又通过char*进行一个字节一个字节的访问,比如
第二个就是使用了回调函数
九.指针和数组笔试题解析
一维数组
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));//16
printf("%d\n", sizeof(a + 0));//4/8
printf("%d\n", sizeof(*a));//4
printf("%d\n", sizeof(a + 1));//4/8
printf("%d\n", sizeof(a[1]));//4
printf("%d\n", sizeof(&a));//4/8
printf("%d\n", sizeof(*&a));//16
printf("%d\n", sizeof(&a + 1));//4/8
printf("%d\n", sizeof(&a[0]));//4/8
printf("%d\n", sizeof(&a[0] + 1));//4/8
return 0;
}
- sizeof(a)中的a是数组名,但是这里有sizeof,所以计算的是整个数组的大小,则结果为16
- sizeof(a+0)中的a+0表示这个数组第一个元素的地址,则结果为4/8,
在32位平台下地址的大小为4,在64位平台下地址的大小为8,
因为在32位机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,64位机器也同理, - sizeof(*a)中的*a为对数组名进行解引用,则找到数组的第一个元素,则结果为4
- sizeof(a+1)中的a为首元素的地址,a + 1是第二个元素的地址,则结果为4/8,
- sizeof(a[1])中的a[1]是数组的第2个元素,因为数组下标是从0开始的,则结果为4,
- sizeof(&a)中的&a表示取数组的地址,虽然是数组的地址,但是也是地址,则结果为4/8
- sizeof(*&a)中的&a表示取数组的地址,再解引用,则*&a表示这整个数组,则结果为16,
- sizeof(&a+1)中的&a表示取整个数组的地址,&a + 1 表示跳过一个数组,指向的是数组后面的空间的地址,结果为4/8
- sizeof(&a[0])中的a[0]表示的是数组的第一个元素,&a[0]取出首元素的地址,则结果为4/8
- sizeof(&a[0]+1)中的a[0]表示的是数组的第一个元素,&a[0]取出首元素的地址,&a[0]+1表示数组的第二个元素的地址,结果是4/8,
字符数组
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));//6
printf("%d\n", sizeof(arr + 0));//4/8
printf("%d\n", sizeof(*arr));//1
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4/8
printf("%d\n", sizeof(&arr + 1));//4/8
printf("%d\n", sizeof(&arr[0] + 1));//4/8
return 0;
}
- sizeof(arr)中的arr是数组名,但是这里有sizeof,所以计算的是整个数组的大小,则结果为16
- sizeof(arr+0)中的arr+0表示这个数组第一个元素的地址,则结果为4/8,
- sizeof(*arr)中的*arr为对数组名进行解引用,则找到数组的第一个元素,则结果为1
- sizeof(arr[1])中的arr[1]是数组的第2个元素,因为数组下标是从0开始的,则结果为1
- sizeof(&arr)中的&arr表示取数组的地址,虽然是数组的地址,但是也是地址,则结果为4/8
- sizeof(&arr+1)中的&arr表示取整个数组的地址,&a + 1 表示跳过一个数组,指向的是数组后面的空间的地址,结果为4/8
- sizeof(&arr[0]+1)中的arr[0]表示的是数组的第一个元素,&a[0]取出首元素的地址,&a[0]+1表示数组的第二个元素的地址,则结果是4/8,
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));//随机值
printf("%d\n", strlen(arr + 0));//随机值
printf("%d\n", strlen(*arr));//err
printf("%d\n", strlen(arr[1]));//err
printf("%d\n", strlen(&arr));//随机值
printf("%d\n", strlen(&arr + 1));//随机值-6
printf("%d\n", strlen(&arr[0] + 1));//随机值-1
return 0;
}
- strlen(arr)中的arr是数组名,表示数组首元素的地址,strlen是从数组首元素的地址开始读取,但是这个arr数组中没有\0所以导致停不下来,则结果为随机值
- strlen(arr+0)中的arr是数组名,表示数组首元素的地址,arr+0还是数组首元素的地址,strlen是从数组首元素的地址开始读取,但是这个arr数组中没有\0所以导致停不下来,则结果为随机值
- strlen(*arr)中的*arr表示这个数组的第一个元素a,a字符的ASCII码值是97,相当于传了个97给strlen,strlen认为97是个地址,然后访问,会出错,则结果为err,
strlen在库函数中为size_t strlen ( const char * str );它只能接收一个字符型的地址,访问从首元素地址到/0结束,然后返回字符串的长度,字符串的长度等于字符串开头和结束的空字符之间的字符数(不包括结束的空字符即/0本身) - strlen(arr[1])中的arr[1]表示这个数组的第二个元素b,b字符的ASCII码值是98,相当于传了个98给strlen,strlen认为98是个地址,然后访问,会出错,则结果为err,
- strlen(&arr)中的&arr表示数组的地址,strlen是从数组首元素的地址开始读取,但是这个arr数组中没有\0所以导致停不下来,则结果为随机值
- strlen(&arr+1)中的&arr表示数组的地址,&arr + 1 表示跳过一个数组,指向的是数组后面空间的地址,但是还是不知道\0在哪里,但这个访问的个数又比最上面那个的个数少六,则结果为随机值-6,
- strlen(&arr[0]+1)中的arr[0]表示的是数组的第一个元素,&a[0]取出首元素的地址,&a[0]+1表示数组的第二个元素的地址,strlen从数组的第2个元素的地址开始访问,但是还是不知道\0在哪里,但这个访问的个数又比最上面那个的个数少一,则结果为随机值-1,
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//7
printf("%d\n", sizeof(arr + 0));//4/8
printf("%d\n", sizeof(*arr));//1
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4/8
printf("%d\n", sizeof(&arr + 1));//4/8
printf("%d\n", sizeof(&arr[0] + 1));//4/8
return 0;
}
- sizeof(arr)中计算的是这个数组的大小,这个"abcdef"中自动补了个\0,且隐藏了,实际上arr数组内容为"abcdef\0" ,则结果为7
- sizeof(arr+0)中 arr + 0 表示这个数组第一个元素的地址,则结果为4/8
- sizeof(*arr)中的*arr为对数组名进行解引用,则找到数组的第一个元素,则结果为1
- sizeof(arr[1])中的arr[1]表示数组的第2个元素,因为数组下标是从0开始的,则结果为1
- sizeof(&arr)中&arr表示取整个数组的地址,虽然是数组的地址,但是也是地址,则结果为4/8
- sizeof(&arr+1)中&arr表示取整个数组的地址,&arr + 1 表示跳过一个数组,指向的是数组后面空间的地址,结果为4/8
- sizeof(&arr[0]+1)中的arr[0]表示的是数组的第一个元素,&arr[0]取出首元素的地址,&arr[0]+1表示数组的第二个元素的地址,sizeof(&arr[0]+1)计算的是数组第2个元素的地址大小,结果是4/8,
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "abcdef";
printf("%d\n", strlen(arr));//6
printf("%d\n", strlen(arr + 0));//6
printf("%d\n", strlen(*arr));//err
printf("%d\n", strlen(arr[1]));//err
printf("%d\n", strlen(&arr));//6
printf("%d\n", strlen(&arr + 1));//随机值
printf("%d\n", strlen(&arr[0] + 1));//5
return 0;
}
- strlen(arr)中的arr是数组名,表示数组首元素的地址,strlen是从数组首元素的地址开始读取,这个arr数组中有\0,strlen读取到\0结束,然后返回字符串的长度,则结果为6
- strlen(arr+0)中的arr是数组名,表示数组首元素的地址,arr+0还是表示数组首元素的地址strlen从数组首元素的地址开始读取,这个arr数组中有\0,strlen读取到\0结束,然后返回字符串的长度,则结果为6
- strlen(*arr)中的*arr表示这个数组的第一个元素a,a字符的ASCII码值是97,相当于传了个97给strlen,strlen认为97是个地址然后访问,则会出错,结果为err,
- strlen(arr[1])中的arr[1]表示这个数组的第二个元素b,b字符的ASCII码值是98,相当于传了个98给strlen,strlen认为98是个地址然后访问,则会出错,结果为err,
- strlen(&arr)中的&arr表示数组的地址,strlen是从数组首元素的地址开始读取,这个arr数组中有\0,strlen读取到\0结束,然后返回字符串的长度,则结果为6
- strlen(&arr+1)中的&arr取出的是数组的地址,&arr + 1 表示跳过一个数组,指向的是数组后面空间的地址,但是不知道\0在哪里,所以所以结果为随机值,
- strlen(&arr[0]+1)中的arr[0]表示的是数组的第一个元素,&arr[0]取出首元素的地址,&arr[0]+1表示数组的第二个元素的地址,strlen从数组的第2个元素的地址开始访问,这个arr数组中有\0,strlen读取到\0结束,然后返回字符串的长度,则结果为5
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
char* p = "abcdef";
printf("%d\n", sizeof(p));//4/8
printf("%d\n", sizeof(p + 1));//4/8
printf("%d\n", sizeof(*p));//1
printf("%d\n", sizeof(p[0]));//1
printf("%d\n", sizeof(&p));//4/8
printf("%d\n", sizeof(&p + 1));//4/8
printf("%d\n", sizeof(&p[0] + 1));//4/8
return 0;
}
- sizeof(p)中的p是一个字符型指针变量,且指向a的地址,则结果是4/8
- sizeof(p+1)中的p+1指向b的地址,则结果是4/8
- sizeof(*p)中的*p表示对p进行解引用,指向a的地址,然后访问,则结果是1
- sizeof(p[0])中的p[0]等价于*(p+0),表示指向a的地址,然后访问,则结果是1
- sizeof(&p)中的&p表示取p的地址,结果为4/8
- sizeof(&p+1)中的&p表示取p的地址,&p+1则跳过p的地址,则结果为4/8
- sizeof(&p[0]+1)中的&p[0]+1表示b的地址,则结果还是4/8
二维数组
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//48
printf("%d\n", sizeof(a[0][0]));//4
printf("%d\n", sizeof(a[0]));//16
printf("%d\n", sizeof(a[0] + 1));//4/8
printf("%d\n", sizeof(*(a[0] + 1)));//4
printf("%d\n", sizeof(a + 1));//4/8
printf("%d\n", sizeof(*(a + 1)));//16
printf("%d\n", sizeof(&a[0] + 1));//4/8
printf("%d\n", sizeof(*(&a[0] + 1)));//16
printf("%d\n", sizeof(*a));//16
printf("%d\n", sizeof(a[3]));//16
return 0;
}
- sizeof(a)中的a是数组首元素地址,但是sizeof(数组名)计算的是数组的大小,则结果是48
- sizeof(a[0][0])中的a[0][0]表示这个数组的第一行第一列的元素,则结果是4
- sizeof(a[0])中的a[0]表示这个数组的第一行,a[0]相当于第一行的数组名,sizeof(数组名)计算的是数组的大小,则结果为16
二维数组在内存中是连续存放的
实际上在内存中是这样的,见下表:
- sizeof(a[0]+1)中的a[0]表示这个数组的第一行,a[0]相当于第一行的数组名,a[0]作为数组名并没有单独放在sizeof内部,也没取地址,所以a[0]就是第一行第一个元素的地址,a[0]+1就是第一行第二个元素的地址,则结果为4/8
- sizeof(*(a[0]+1)中的a[0]+1就是第一行第二个元素的地址,*(a[0]+1)表示第一行第二个元素,则结果为4
- sizeof(a+1)中的a是二维数组的数组名,并没有取地址,也没有单独放在sizeof内部,所以a就表示二维数组首元素的地址,所以表示第一行的地址,a + 1就是二维数组第二行的地址,则结果为4/8
- sizeof(*(a + 1))中的a + 1是二维数组第二行的地址,*(a + 1)表示二维数组第二行的数组名sizeof(数组名)计算的是二维数组第二行的大小,则结果为4
- sizeof(&a[0] + 1)中a[0]是第一行的数组名,&a[0]取出的就是第一行的地址,&a[0]+1 就是第二行的地址,则结果为4/8
- sizeof(*(&a[0] + 1))中&a[0]+1就是第二行的地址,*(&a[0]+1) 就是第二行,所以计算就是第二行的大小,则结果为16
- sizeof(*a)中的a作为二维数组的数组名,没有&,没有单独放在sizeof内部,a就是首元素的地址,即第一行的地址,所以*a就是第一行,计算的是第一行的大小,结果为16
- sizeof(a[3])中的a[3]其实是第四行的数组名(如果有的话),a[3]的类型属性-int [4],其中sizeof()内部的表达式不计算,所以其实不存在,也能通过类型计算大小的,结果为16
表达式有两个属性,一个是值属性,另一个是类型属性,
如3+5,值属性-8,类型属性-int,则sizeof(3+5)等于4
总结: 数组名的意义如下
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。
十.指针笔试题
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));//2,5
return 0;
}
&a表示取出这个数组的地址,&a+1跳过一个数组,指向的是数组后面空间的地址,它的原类型为Int(*)[5],再将其强制类型转换为int*,再将其赋给指针变量ptr
*(ptr - 1)解释:ptr向后跳过一个int,然后解引用找到并且访问5
*(a+1)解释:a为数组首元素,a+1为数组的第二个元素,然后解引用找到并且访问2
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。如下表表达式的值为多少
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
考查的是:指针类型决定了指针的运算
- p+0x1中p为结构体指针变量,这个结构体的大小为20,0x1实际上就是1,p+1会跳过一个结构体的大小,指向的是数组后面空间的地址0x100000+20=0x100014,结果为0x100014
- (unsigned long)p + 0x1中将p强制类型转换为unsigned long,它加1就是加1,0x100000+1=0x100001
- (unsigned int*)p+0x1中将p强制类型转换为unsigned long*,p变成了无符号整形指针,它加一就是加一个int,0x100000+4=0x100004
、
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
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);//4,2000000
return 0;
}
- ptr1是一个整形指针,指向的是数组后面空间的地址,&a取出的是数组的地址
-
ptr2是一个整形指针,(int)a + 1中a表示首元素的地址,再将其强制类型转换问int,它加一就是加一(地址加1),相当于向后偏移了一个字节,在内存中一个字节给一个地址,如:0x0012ff44-->int+1-->0x0012ff45
在小端机器下,
- *ptr2表示对ptr2进行解引用,找到并访问4个字节,ptr1[-1]等价于*(ptr1-1),结果为4,2000000
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);//1
return 0;
}
(0, 1), (2, 3), (4, 5)这几个为逗号表达式,等价于1,3,5,
a[0]表示这个二维数组的首元素的地址,p为整形指针变量,p[0]等价于*(p+0),结果为1
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
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]);//FFFFFFFC,-4
return 0;
}
p=a中a的类型为int(*)[5],p的类型为int (*)[4],p[4][2]等价于*(*(p+4)+2),&p[4][2] - &a[4][2]计算的是两者之间的元素个数,随着数组下标的增长,地址是由低地址到高地址变化的,所以结果为-4,
-4以%d的形式打印还是-4
-4的原码10000000000000000000000000000100
-4的反码111111111111111111111111111111111011
-4的补码111111111111111111111111111111111100
-4在内存中以补码的形式存储,%p的形式打印,会直接将-4的补码当作原码打印出来所以结果为FFFFFFFC
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
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));//10,5
return 0;
}
- int* ptr1 = (int*)(&aa + 1)中&aa是取出这个数组的地址,&aa+1则跳过一个数组,再将其强制类型转换为int*,再将其赋给指针变量ptr,ptr1是一个整形指针,指向的是数组后面空间的地址,*(ptr1 - 1)的结果是10
- int* ptr2 = (int*)(*(aa + 1))中aa是二维数组的数组名,是第一行的地址,aa+1则跳过第一行,指向第二行,再将其强制类型转换为int*,再将其赋给指针变量ptr2,ptr2是一个整形指针,ptr2为第二行首元素的地址,*(ptr2 - 1)的结果是5
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);//at
return 0;
}
char* a[]是一个字符型指针数组,a为数组名,是数组首元素的地址,
char** pa = a中a是一个一级指针的地址,所以用二级指针接收刚好,pa是一个二级指针变量,指向的是数组首元素的地址,结果是at
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp;
printf("%s\n", **++cpp);//POINT
printf("%s\n", *-- * ++cpp + 3);//ER
printf("%s\n", *cpp[-2] + 3);//ST
printf("%s\n", cpp[-1][-1] + 1);//EW
return 0;
}
- char*c[],char**cp[],char***cpp这三者之间的指向关系如下:
- **++cpp表示先cpp+1,再解引用,指向c+2的地址,再解引用,指向P的地址,结果为POINT
- *-- * ++cpp + 3表示先cpp+1,由于上面的运算cpp变成了cpp+1,所以这里的cpp变成了cpp+2,再解引用,指向c+1的地址,再减1,指向c的地址,再解引用,指向E的地址,再加3,指向第四个E的地址,结果为ER
- *cpp[-2] + 3等价于*(*(cpp-2))+3表示为cpp-2,由于上面的运算cpp变成了cpp+2,所以这里的cpp变成了cpp,再解引用,指向c+3地址,再解引用,指向F的地址,再加3,指向S的地址,结果为ST
- cpp[-1][-1] + 1等价于*(*(cpp-1)-1)+1,由于上面的运算cpp变成了cpp+2,所以这里的cpp变成了cpp+1,再解引用,指向c+2的地址,再减1,指向c+1的地址,再解引用,指向N的地址,再加一指向E的地址,结果为EW
//阅读两段有趣的代码
//代码1
(*(void(*)())0)();//出自《C陷阱和缺陷》
//代码2
void(*signal(int , void(*) (int)))(int);//出自《C陷阱和缺陷》
- 解析代码1:突破口在0那里,调用0地址处的函数,该函数无参,返回类型是void
(1)void(*)() - 函数指针类型
(2) (void(*)())0 - 对0进行强制类型转换,被解释为一个函数地址
(3)*(void(*)())0 - 对0地址进行解引用操作
(4)(*(void(*)())0)() - 调用0地址处的函数 - 解析代码2:突破口在signal那里,是一个函数是声明
(1)signal先和()结合,说明signal是函数名,
(2)signal函数的第一个参数的类型是int,第二个参数的类型是,该函数指针指向一个参数为int,返回类型为void的函数
(3)signal函数的返回类型也是一个函数指针,该函数指针指向一个参数为int,返回类型为void的函数
简化代码2:
- void(*)(int) signal (int,void(*)(int)) 这样写更好理解,但是在语法上第一个的*必须跟signal结合在一起,
- 这里我们可以使用typedef,它可以对类型进行重定义,typedef void(*)(int) pfun_t
- 但是在语法上第一个的*必须跟pfun_t结合在一起,所以正确写法为typedef void(*pfun_t)(int)
- 最后简化后的代码为pfun_t signal (int , pfun_t)
--------------------------------------------------------------------------------------------------------------------------------本章完