目录
- 前言
- 指针概念
- 一、字符指针
- 例题一
- 二、指针数组
- 例题一
- 例题二
- 例题三
- 三、数组指针
- 3.1数组指针的定义
- 例题一
- 例题二
- 3.2 &数组名VS数组名
- 3.3数组指针的使用
- 例题一
- 例题二
- 例题三
- 四、 数组参数、指针参数
- 4.1 一维数组传参
- 4.2 二维数组传参
- 4.3 一级指针传参
- 4.4 二级指针传参
- 五、函数指针
- 例题一
- 例题二
- 六、函数指针数组
- 七、指向函数指针数组的指针
- 八、回调函数
- 使用回调函数,模拟实现qsort(采用冒泡的方式)
前言
指针概念
- 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
- 指针的大小是固定的
4/8
个字节(32
位平台/64
位平台)。 - 指针是有类型,指针的类型决定了指针的
+-
整数的步长,指针解引用操作的时候的权限。 - 指针的运算。
一、字符指针
int main()
{
char ch = 'w';
char *pc = *ch;//pc是指向一个常量字符串
const char *p = "helloworld";
//"helloworld"是一个常量字符串,存在在内存的常量区。不能更改
//上面表达式的意思是把常量表达式"helloworld"的第一个字符h的地址赋值给p
printf("%c\n", *p);//h
printf("%s\n", p);//helloworld
return 0;
}
例题一
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char *str3 = "hello bit.";//常量字符串,不能修改
const char *str4 = "hello bit.";
if (str1 == str2)
printf("str1 and str2 are same\n"); //用相同的常量字符串去初始
//化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同
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;
}
运行结果:
这里str3
和str4
指向的是同一个常量字符串。C/C++
会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1
和str2
不同,str3
和str4
不同。
二、指针数组
指针数组是一个存放指针的数组。
char *arr[5];// arr是存放字符指针的数组
int *arr[5];// arr是存放整形指针的数组
例题一
int main()
{
int a = 0;
int b = 20;
int c = 30;
int d = 40;
int *arr[4] = {&a, &b, &c, &d};
//arr就是整形指针的数组。数组共4个元素,每个元素都是int*类型
for (int i = 0; i < 4; i++)
{
printf("%d ", *(arr[i])); //0 20 30 40
}
return 0;
}
例题二
int main()
{
int arr1[] = { 1, 2, 3, 4, 5 };
int arr2[] = {6, 7, 8, 9, 10};
int arr3[] = { 2, 3, 4, 5, 6 };
int *parr[] = { arr1, arr2, arr3 };//存储每个数组的首地址
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d ", parr[i][j]);//p[i] = *(p +i)
//parr[i][j] = *(parr[i] +j)
}
printf("\n");
}
return 0;
}
输出结果:
例题三
int main()
{
const char *arr[5] = {"abcdef","bcdef","hehe","zhangsan","haha"};
//字符指针的数组,指针里面存储每个字符串的首地址。
int i = 0;
for ( i = 0; i < 5; i++)
{
printf("%s\n", arr[i]);
}
return 0;
}
三、数组指针
3.1数组指针的定义
数组指针是指针,是能够指向数组的指针。
int *p1[10];//指针数组
int (*p2)[10];//数组指针
例题一
int main()
{
int arr[10] = { 0 };
int *p = arr;//arr是数组首元素的地址
int (*parr)[10] = &arr;//取出的是数组的地址,应该存放在数组指针中
//parr类型为int(*)[10]
return 0;
}
对int (*p)[10]
的解释:
解释:p
先和*
结合,说明p
是一个指针变量,然后指向的是一个大小为10
个整型的数组。所以p
是一个指针,指向一个数组,叫数组指针
。
【注意】:[]
的优先级要高于*
号的,所以必须加上()
来保证p
先和*
结合。
例题二
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);//数组名是首元素的地址(arr arr[0]),类型为int*
printf("%p\n", &arr[0]);//类型为int*
printf("%p\n", &arr);//取出数组的地址(&arr) 类型为int*(10)
//上面三行输出的结果一样,都为012FF804
printf("%p\n", arr +1); //012FF808
printf("%p\n", &arr[0] + 1); //012FF808
printf("%p\n", &arr + 1); //012FF82C 数组指针+1,跳过一个数组
return 0;
}
3.2 &数组名VS数组名
数组名是首元素地址,但是有两个例外:
1.sizeof(arr)/sizeof(数组名)
这里的数组名是表示整个数组,sizeof(数组名)
计算的是整个数组的大小,单位是字节
2.&(数组名)
,这里的数组名不是首元素的地址,数组名表示整个数组,所以取出的是整个数组的地址。
数组的地址+1
,跳过整个数组的大小。
3.3数组指针的使用
例题一
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 print3(int (*parr)[10], int sz)//这是一个错误的示范,这是parr指向的是整个数组,+1跳过的是整个数组
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", parr[i]);//parr[i] == *(parr+i)
}
}
void print4(int (*parr)[10], int sz)
{
//*(parr + 0);-->parr[0]
int i = 0;
for (i = 0; i < sz; i++)
{
//printf("%d ", parr[0][i]);
//printf("%d ", (*(parr + 0))[i]);
printf("%d ", (*parr)[i]);//(*parr) 相当于 parr指向的数组的数组名
}
}
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);
//print3(&arr, sz);
//print2(arr, sz);
//print1(arr, sz);//打印arr数组的内容
return 0;
}
例题二
void printf1(int arr[3][5], int r, int c)
{
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void printf2(int (*p)[5], int r, int c)
{
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; 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 };
//二维数组传参
//printf1(arr, 3, 5);
printf2(arr, 3, 5); //arr是数组名,数组名是首元素(二维数组的第一行)地址(是二维数组每一行的一维数组首元素的地址)可以数组指针来接收
return 0;
}
例题三
int arr[5];
:整形数组
int *parr1[10]
;parr1
是一个数组,10
个元素,每个元素是int*
的,所以parr1
是一个存放指针的数组。
int (*parr2)[10]
;parr2
是一个数组指针,该指针指向的数组有10
个元素,每个元素都是int
的。
int (*parr3[10])[5]
;parr3
是一个数组,数组有10
个元素,每个元素是一个数组指针,该指针指向的数组有5
个元素,每个元素是int
的。
四、 数组参数、指针参数
4.1 一维数组传参
#include <stdio.h>
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int *arr)//ok
{}
void test2(int* arr[20])//ok,20可以省略
{}
void test2(int** arr)//ok,一级指针的地址传过去,要用一个二级指针接收
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
4.2 二维数组传参
void test(int arr[3][5])//ok
{}
void test(int arr[][])//错误
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//错误
{}
void test(int* arr[5])//错误
{}
void test(int (*arr)[5])//ok 二维数组首元素指的是第一行。
{}
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\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)
{}
int main()
{
int a = 10;
int *p1 = &a;
int arr[10] = { 0 };
test(&a);
test(arr);
test(p1);
return 0;
}
4.4 二级指针传参
当函数的参数为二级指针的时候,可以接收什么参数?
#include <stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p;
int *arr[5];
test(pp);
test(&p);
test(arr);
return 0;
}
五、函数指针
函数指针变量:存放函数的地址
函数名
和&函数名
是一样的。
int Add(int x, int y)
{
return x + y;
}
int main()
{
//printf("%p\n", Add);
int(*pf)(int, int) = &Add;//pf是用来存放函数的地址,-pf就是函数指针变量
//函数指针类型:int(*)(int, int)
int ret = Add(4, 5);
printf("%d\n", ret);
ret = (*pf)(4, 5); //*可以没有,也可以写多个
ret = pf(4, 5);
printf("%d\n", ret);//9
return 0;
}
例题一
(*(void (*)())0)();
这个代码是一次函数调用。
void(*)()
函数指针类型,将0
强制类型转换为(void(*)())
的一个函数的地址,*(void(*)())
解引用0
地址,找到那个函数,被调用的函数是无参,返回类型是void
,后面的括号表明不用传参。
例题二
void (*signal(int , void(*)(int)))(int);
//也可以写为:
typedef void(*pfun_t)(int);
pfun_t signal2(int, pfun_t);
这个代码是一次函数声明。
函数名是signal
,signal
函数有两个参数,第一个是int
类型,第二个是函数指针类型void(*)(int)
,signal
函数的返回类型是void(*)(int)
的函数指针类型。
六、函数指针数组
数组是一个存放相同类型数据的存储空间,那把函数的地址存到一个数组中,那这个数组就叫函数指针数组。
计算器的模拟实现:
方法一:
#include <stdio.h>
int add(int a, int b) //int(*)(int,int)
{
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 input = 0;
do
{
int x = 0;
int y = 0;
int ret = 0;
int(*pArr[5])(int, int) = { 0, add, sub, mul, div };//函数指针数组
//转移表
printf("*************************\n");
printf("***** 1:add 2:sub *******\n");
printf(" *****3:mul 4:div ******\n");
printf("******0.exit*************\n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
}
else if ((input <= 4 && input >= 1))
{
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*pArr[input])(x, y);
printf("ret = %d\n", ret);
}
else
{
printf("输入有误\n");
}
} while (input);
return 0;
}
方法二:
#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;
}
void Calc(int(*pf)(int,int))
{
int x = 0;
int y = 0;
int ret = 0;
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = pf(x, y);
printf( "ret = %d\n", ret);
}
int main()
{
int input = 1;
do
{
printf("*************************\n");
printf("***** 1:add 2:sub *******\n");
printf(" *****3:mul 4:div ******\n");
printf("******0.exit*************\n");
printf("*************************\n");
printf( "请选择:" );
scanf( "%d", &input);
switch (input)
{
case 1:
Calc(add);
break;
case 2:
Calc(del);
break;
case 3:
Calc(mul);
break;
case 4:
Calc(div);
case 0:
printf("退出程序\n");
breark;
default:
printf( "选择错误\n" );
break;
}
} while (input);
return 0;
}
七、指向函数指针数组的指针
指向函数指针数组的指针是一个指针。
指针指向一个数组 ,数组的元素都是函数指针。
int Add(int x,int y)
{
return x+y;
}
int (*pf)(int,int) = Add;//pf是函数指针
int (*pfArr[5])(int,int);//pfArr是一个函数指针数组
int (*(*pfArr)[5])(int,int)= &pfArr;
//ppfArr 就是一个指向一个函数指针数组的指针
八、回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
使用回调函数,模拟实现qsort(采用冒泡的方式)
#include <stdio.h>
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2; //升序
}
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for ( i = 0; i < width; i++)
{
int temp = *buf1;
*buf1 = *buf2;
*buf2 = temp;
buf1++;
buf2++;
}
}
void Bubbble_sort(void* base, size_t num, size_t width, int(*cmp)(const void* e1, const void* e2))
{
size_t i = 0;
for ( i = 0; i < num - 1; i++) //趟数
{
size_t 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);
}
}
}
}
//打印函数
void print_arr(int arr[], int sz)
{
int i = 0;
for ( i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[] = { 10,9,7,6,5, 4, 3, 2, 1 };
int sz = sizeof(arr) / sizeof(arr[0]);
Bubbble_sort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
return 0;
}
以上。如有错误,还请指正。