指针的概念:
- 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
- 指针的大小是固定的4/8个字节(32位平台/64位平台)。
- 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
- 指针的运算。
1. 字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char* 。
一般使用:
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
还要一种使用方式如下:
int main()
{
char arr[] = "abcdef";
char* pc = arr;
printf("%s\n", arr);
printf("%s\n", pc);
return 0;
}
int main()
{
const char* p = "abcdef";
printf("%s\n", p);
printf("%c\n", *p);
printf("%s\n", p);
return 0;
}
代码char* p = "abcdef"; ,本质是把字符串abcdef 首字符的地址放到了p中。
面试题 (来源于《剑指offer》):
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdef";
char* p1 = "abcdef";
char* p2 = "abcdef";
if (arr1 == arr2)
{
printf("arr1 == arr2\n");
}
else
{
printf("arr1 != arr2\n");
}
if (p1 == p2)
{
printf("p1 == p2\n");
}
else
{
printf("p1 != p2\n");
}
return 0;
}
最终运行结果:
这里 p1 和 p2 指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,它们实际会指向同一块内存,但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以 arr1 和 arr2 不同,p1 和 p2 相同。
2. 指针数组
指针数组 是一个存放指针的数组。
int* arr1[10];
char* arr2[4];
char** arr3[5];
int main()
{
int a = 10;
int b = 20;
int c = 30;
int d = 40;
int* arr[4] = {&a, &b, &c, &d};
int i = 0;
for(i = 0;i<4;i++)
{
printf("%d ",*arr[i]);
}
return 0;
}
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 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", *(parr[i] + j));
}
printf("\n");
}
return 0;
}
int main()
{
const char* arr[5] = {"abcdef","bcdefg","hehe","haha","heihei"};
int i = 0;
for(i = 0; i<5; i++)
{
printf("%s\n", arr[i]);
}
return 0;
}
3. 数组指针
3.1 数组指针的定义
数组指针是 能够指向数组的指针。
int main()
{
int a = 10;
int* pa = &a;
char ch = 'w';
char*pc = &ch;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int(*parr)[10] = &arr;
return 0;
}
下面代码哪个是数组指针?
int *p1[10];
int (*p2)[10];
解释:
int (*p)[10];
3.2 &数组名 VS 数组名
根据代码看出,arr 是数组名,虽然 arr 和&arr 的值是一样的,但是 &arr 表示的是 数组的地址。
arr 的类型是int* ,&arr 的类型是int(*)[10] ,指针的类型决定了指针加减 1 跳过的长度。
3.3 数组指针的使用
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
看代码:
自讨苦吃的写法:
#include <stdio.h>
void print1(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
void print2(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(arr + i));
}
}
void print4(int(*parr)[10], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(*parr + i));
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
print4(&arr, sz);
return 0;
}
一个数组指针的正确使用:
void print1(int arr[3][5], int x, int y)
{
int i = 0;
int j = 0;
for (i = 0; i < x; i++)
{
for (j = 0; j < y; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print2(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} };
print2(arr, 3, 5);
return 0;
}
下面代码的意思:
int arr[5];
int* parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];
4. 数组参数、指针参数
在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?
4.1 一维数组传参
#include <stdio.h>
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}
void test2(int *arr[20])
{}
void test2(int *arr[])
{}
void test2(int **arr)
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
4.2 二维数组传参
void test(int arr[3][5])
{}
void test(int arr[][5])
{}
void test(int *arr)
{}
void test(int* arr[5])
{}
void test(int (*arr)[5])
{}
void test(int **arr)
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
4.3 一级指针传参
#include <stdio.h>
void print(int *p, int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
print(p, sz);
return 0;
}
思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
void test1(int* p)
{}
int main()
{
int a = 10;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &a;
test1(&a);
test1(p);
test1(arr);
}
4.4 二级指针传参
void test(int** ptr)
{
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);
test(&p);
int* arr[10];
test(arr);
return 0;
}
思考:
当函数的参数为二级指针的时候,可以接收什么参数?
void test(char **p)
{
}
int main()
{
char c = 'b';
char*pc = &c;
char**ppc = &pc;
char* arr[10];
test(&pc);
test(ppc);
test(arr);
return 0;
}
5. 函数指针
数组指针 ——> 是指向数组的指针。
函数指针 ——> 是指向函数的指针 – 存放函数地址的一个指针。
存放指针的地址:
int Add(int x, int y)
{
return x + y;
}
int main()
{
int arr[10] = {0};
int (*parr)[10] = &arr;
int (*pa)(int, int) = Add;
printf("%d\n", (*pa)(2, 3));
return 0;
}
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pa)(int, int) = Add;
printf("%d\n", (*pa)(2, 3));
printf("%d\n", (pa)(2, 3));
printf("%d\n", pa(2, 3));
printf("%d\n", Add(2, 3));
return 0;
}
void Print(char* str)
{
printf("%s\n", str);
}
int main()
{
void (*p)(char*) = Print;
(*p)("hello bit");
return 0;
}
阅读两段有趣的代码:
注 :推荐《C陷阱和缺陷》 – 这本书中提及这两个代码。
(*(void (*)())0) ();
void (*signal(int , void(*)(int)))(int);
将代码2进行简化:
void(*signal(int, void(*)(int)))(int);
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
6. 函数指针数组
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:
int* arr[10];
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[10])();
练习1
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* arr[5];
int (*pa)(int, int) = Add;
int (*parr[4])(int, int) = { Add,Sub,Mul,Div };
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d\n",parr[i](2, 3));
}
return 0;
}
练习2
char* my_strcpy(char* dest, const char* src);
char* (*pf)(char*, const char*);
char* (*pfArr[4])(char*, const char*);
函数指针数组的用途:转移表。-- 《C和指针》
例子:(计算器)
void menu()
{
printf("**********************************\n");
printf("********* 1.add 2.sub ********\n");
printf("********* 3.mul 4.div ********\n");
printf("********* 0.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;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
printf("%d\n", Add(x, y));
break;
case 2:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
printf("%d\n", Sub(x, y));
break;
case 3:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
printf("%d\n", Mul(x, y));
break;
case 4:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
printf("%d\n", Div(x, y));
break;
case 0:
printf("退出\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
改进代码:
void Calc(int (*pf)(int,int))
{
int x = 0;
int y = 0;
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
printf("%d\n", pf(x, y));
}
int main()
{
int input = 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");
break;
}
} while (input);
return 0;
}
使用函数指针数组的实现:
void menu()
{
printf("**********************************\n");
printf("********* 1.add 2.sub ********\n");
printf("********* 3.mul 4.div ********\n");
printf("********* 5.xor 0.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 (*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);
ret = pfArr[input](x, y);
printf("%d\n", ret);
}
else if (input == 0)
{
printf("退出\n");
break;
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
7. 指向函数指针数组的指针
指向函数指针数组的指针是一个 指针 ,指针指向一个 数组,数组的元素都是 函数指针。
int Add(int x, int y)
{
return x + y;
}
int main()
{
int arr[10] = { 0 };
int(*p)[10] = &arr;
int* arr[10];
int* (*p)[10] = &arr;
int (*pf)(int, int);
int (*pfArr[4])(int, int);
int (*(*ppfArr)[4])(int, int) = &pfArr;
return 0;
}
8. 回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一 个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该 函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或 条件进行响应。
void* 指针的用法:
int main()
{
int a = 10;
void* p = &a;
return 0;
}
首先演示一下qsort函数的使用:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void bubble_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
struct Stu
{
char name[20];
int age;
};
int cmp_int(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2);
}
void test1()
{
int arr[10] = { 1,3,5,7,9,2,4,6,8,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int cmp_float(const void* e1, const void* e2)
{
if ((*(float*)e1 == *(float*)e2))
return 0;
else if ((*(float*)e1 > *(float*)e2))
return 1;
else
return -1;
}
void test2()
{
float f[] = { 1.1,1.2,1.4,1.3 };
int sz = sizeof(f) / sizeof(f[0]);
qsort(f, sz, sizeof(f[0]), cmp_float);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%.3f ", f[i]);
}
}
int cmp_stu_by_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 s[3] = { {"zhangsan",20},{"lisi",21},{"wangwu",18} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
test3();
return 0;
}
使用回调函数,模拟实现qsort(采用冒泡的方式)。
struct Stu
{
char name[20];
int age;
};
int cmp_int(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2);
}
int cmp_stu_by_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 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, size_t sz, size_t width,int (*cmp)(void* e1, void* e2))
{
size_t i = 0;
for (i = 0; i < sz - 1; i++)
{
size_t j = 0;
for (j = 0; j < sz - 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);
}
}
}
}
void test4()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);
}
void test5()
{
struct Stu s[3] = { {"zhangsan",20},{"lisi",21},{"wangwu",18} };
int sz = sizeof(s) / sizeof(s[0]);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
test5();
return 0;
}
9. 指针和数组笔试题解析
一维数组解析:
int main()
{
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));
return 0;
}
字符数组解析:
int main()
{
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));
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));
const 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));
return 0;
}
二维数组解析:
int main()
{
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]));
return 0;
}
指针笔试题
笔试题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:
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;
}
解析:
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
printf("%#x\n",p + 0x1);
return 0;
}
笔试题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:
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 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;
}
解析:
笔试题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;
}
笔试题7:
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
笔试题 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;
}
希望可以对大家有所帮助,如果有什么不对的地方,还烦请指教,谢谢!
|