-
字符指针
-
数组指针
-
指针数组
-
函数指针
-
函数指针数组
-
容易混淆的一些指针习题
-
字符指针
char* a="abcde"; //指针a保存的是字符串首元素的地址,而不是字符串的地址;
//注意这里是不能对
// 指针指向的内容做改变的,因为指针指向的是一个常量,只读不能写
char a[]="abcde"
printf("%p",a+1); //直接取数组名是数组首元素的地址,加1==a[1]的地址
printf("%p",&a+1) //注意,这里是取数组地址加1,是跳过整个数组
- 数组指针和指针数组
数组指针不是数组而是指针,列如
int (*p)[] //(*p)加了括号之后*先和p结合,因此是指针,指向一个数组
指针数组
指针数组是指针
int *a[10]; //整形的指针数组,数组里保存的是指针
数组指针代码举例
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col) {
int i = 0;
for(i=0; i<row; i++){
for(int j=0; j<col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int(*arr)[5], int row, int col) {
int i = 0;
for (i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
printf("%d ", arr[i][j]); }
printf("\n"); } }
int main() {
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
print_arr1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}
这两个函数的输出结果是一样的都是
1 2 3 4 5
6 7 8 9 10
0 0 0 0 0
- 函数指针
如果想要保存一个函数的地址就需要创建一个函数指针
以下哪个是函数指针?
1 int (*a)();
2 int *a();
答案是1,1是 * 先和a结合所以为指针,能保存一个返回值为整形的,无参数的函数的地址,而第二种只是定义函数的返回值为int*类型,是函数.
函数指针的用途:回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这 个指针被用来调用其所指向的函数时,我们就说这是回调函数。
例如:
//冒泡排序
//比较函数
#include <stdio.h>
int int_cmp(const void * p1, const void * p2) {
return (*( int *)p1 > *(int *) p2); }
//交换函数
void _swap(void *p1, void * p2, int size) {
int i = 0;
for (i = 0; i< size; i++) {
char tmp = *((char *)p1 + i);
*(( char *)p1 + i) = *((char *) p2 + i);
*(( char *)p2 + i) = tmp; }
}
//bubble最后一个参数定义了一个函数指针,而主函数已将函数int_cmp的地址传给了它,
//注意只能接受相同类型的函数的地址
void bubble(void *base, int count , int size, int(*cmp )(void *, void *)) {
int i = 0;
int j = 0;
for (i = 0; i< count - 1; i++) {
for (j = 0; j<count-i-1; j++) {
//调用cmp函数,将要比较的元素的地址传给cmp函数,如果j号元素大于j+1号元素则
//调用swap函数交换
if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0) {
_swap(( char *)base + j*size, (char *)base + (j + 1)*size, size);
}
}
}
}
int main() {
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++) {
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
- 函数指针数组
函数指针数组是一个数组,数组里保存的是函数的地址
它的定义 int (*p[10]])();
*是先和p[10]结合的而指针数组里能保存地址,在和函数结合就可在数组里保存相同类型函数的地址
函数指针数组的用途:转移表
用例:
//简单计算器利用转移表(函数指针数组)实现
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
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 ret = 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来实现加减乘除的选择相对要麻烦许多
//而利用转移表只需要做合法性判断即可,用户输入选项后即可根据数组保存的地址直接调用到相应的函数
if ((input <= 4 && input >= 1))
{ printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf( "输入有误\n" );
printf( "ret = %d\n", ret);
}
return 0;
}
- 容易混淆的一些指针习题 (//后注明答案,默认在32位系统下)
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a)); //16 直接取数组名指的是整个数组的字节数
printf("%d\n",sizeof(a+0)); //4 转换为指针
printf("%d\n",sizeof(*a)); // 1 下标为0的元素
printf("%d\n",sizeof(a+1)); //4
printf("%d\n",sizeof(a[1])); //4
printf("%d\n",sizeof(&a)); // 4/8 32位/64位系统 指针是固定的字节数
printf("%d\n",sizeof(*&a)); // 16
printf("%d\n",sizeof(&a+1)); // 4 指针
printf("%d\n",sizeof(&a[0])); //4 指针
printf("%d\n",sizeof(&a[0]+1)); // 4 指针加1,往后移动了一个元素,还是指针
//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr)); //6 char类型的字符占一个字节,6个字符即6字节
printf("%d\n", sizeof(arr+0)); //4 指针
printf("%d\n", sizeof(*arr)); //1
printf("%d\n", sizeof(arr[1])); //1
printf("%d\n", sizeof(&arr)); //4指针
printf("%d\n", sizeof(&arr+1)); //4
printf("%d\n", sizeof(&arr[0]+1)); //4
printf("%d\n",sizeof(arr[0]+1); //4 隐式转换为int
printf("%d\n", strlen(arr)); //6
printf("%d\n", strlen(arr+0)); //6
printf("%d\n", strlen(*arr)); //未定义行为
printf("%d\n", strlen(arr[1])); //未定义行为
printf("%d\n", strlen(&arr)); //6
printf("%d\n", strlen(&arr+1)); //未定义行为,访问非法内存,跳过了整个数组
printf("%d\n", strlen(&arr[0]+1)); //5 跳过一个元素
char arr[] = "abcdef";
printf("%d\n", sizeof(arr)); //7 字符串以'\0'结尾
printf("%d\n", sizeof(arr+0)); //4
printf("%d\n", sizeof(*arr)); //1
printf("%d\n", sizeof(arr[1])); //1
printf("%d\n", sizeof(&arr)); //4
printf("%d\n", sizeof(&arr+1)); //4
printf("%d\n", sizeof(&arr[0]+1)); //4
printf("%d\n", strlen(arr)); //6 不包括'\0'
printf("%d\n", strlen(arr+0)); //6
printf("%d\n", strlen(*arr)); //未定义行为
printf("%d\n", strlen(arr[1])); //未定义行为
printf("%d\n", strlen(&arr)); //6
printf("%d\n", strlen(&arr+1)); //未定义行为,跳过了整个数组
printf("%d\n", strlen(&arr[0]+1)); //5
char *p = "abcdef";
printf("%d\n", sizeof(p)); //4 指针
printf("%d\n", sizeof(p+1)); //4
printf("%d\n", sizeof(*p)); //1
printf("%d\n", sizeof(p[0])); //1
printf("%d\n", sizeof(&p)); //4
printf("%d\n", sizeof(&p+1)); //4
printf("%d\n", sizeof(&p[0]+1)); //4
printf("%d\n", strlen(p)); //6
printf("%d\n", strlen(p+1)); //5
printf("%d\n", strlen(*p)); //未定义行为
printf("%d\n", strlen(p[0])); //未定义行为
printf("%d\n", strlen(&p)); //未定义行为,p原本就为指针,再取地址就变成了二级指针
printf("%d\n", strlen(&p+1)); //未定义行为
printf("%d\n", strlen(&p[0]+1)); // 5
//二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a)); //48 12个int类元素
printf("%d\n",sizeof(a[0][0])); //4
printf("%d\n",sizeof(a[0])); //16 第一行4个int元素
printf("%d\n",sizeof(a[0]+1)); //4 a[0]为int的0加1等于1,还是int类型
printf("%d\n",sizeof(*(a[0]+1))); //4
printf("%d\n",sizeof(a+1)); //4
printf("%d\n",sizeof(*(a+1))); //16 第二行
printf("%d\n",sizeof(&a[0]+1)); //4
printf("%d\n",sizeof(*(&a[0]+1))); //16
printf("%d\n",sizeof(*a)); //16 第一行
printf("%d\n",sizeof(a[3])); //数组越界未定义行为
//题目一
int main() {
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1); //注意这里将数组指针强转为了int*,所以减1只跳过一个元素
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0; }
//程序的结果是什么?
//2,5
题2
//此结构体的大小是20个字节
struct Test {
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。
如下表表达式的值分别为多少?
int main() {
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
//结果
//100014 //跳过整个结构体20字节
//100001 //强转为unsigned long形所以直接加1
//100004 //强转为了unsigned int*指针,所以加4
题3
int main() {
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1); //注意:这里先将指针转为了int整形加一,再转为了int*,即以该地址往下的四个
//字节,包含数组第一个元素的三个字节,和数组第二个元素的一个字节,所以输出结果与
//电脑保存数字的方式有关,所以大端序和小端序结果不同
printf( "%x,%x", ptr1[-1], *ptr2);
return 0;
}
//答案
//4, 200000
笔试题4
#include <stdio.h>
int main(int argc, char * argv[]) {
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
}
//结果
//0 //p保存的是数组第一行元素的地址,再对第一行元素的0号元素进行解引用,结果为0
笔试题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;
}
//结果
//FFFFFFFC,-4
笔试题6
int main() {
int a[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)(*(a + 1));
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
//结果
//10,5
笔试题7
#include <stdio.h>
int main() {
char *a[] = {"work","at","alibaba"}; //指针数组,保存了三个字符串的首地址
char**pa = a; //二级指针
pa++;
printf("%s\n", *pa); //解引用一次,还是指针,指向了元素a
return 0;
}
//结果
//at
笔试题8
int main() {
char *c[] = {"ENTER","NEW","POINT","FIRST"}; //指针数组保存了4个字符串的地址
char**cp[] = {c+3,c+2,c+1,c}; char***cpp = cp; //二级指针数组,保存着c+3,c+2,c+1,c,即
//逆序地址
printf("%s\n", **++cpp); //运算顺序 ++ * *
printf("%s\n", *--*++cpp+3); //++ * -- * +3
printf("%s\n", *cpp[-2]+3); //[-2] * +3
printf("%s\n", cpp[-1][-1]+1); //[-1][-1] +1
return 0;
}
//结果
//point
//ER
//ST
//EW