指针是一个变量,用来存放地址。而地址是内存单元的编号。 指针的大小是4字节或8字节。 指针具有多种类型,不同类型的指针看待内存单元的视角不同,管理的内存单元的大小也有差异。 指针可以进行加减运算、比较大小等运算。
接下来是更加详细的的指针专题!
是指向字符的指针,用来存放字符的地址。 重点在后头。
用法1:存放单个字符的地址并解引用。
#include <stdio.h>
int main(){
char ch = 'a';
char* p = &ch;
*p = 'b';
printf("%c", ch);
return 0;
}
用法2:存放字符串的首元素的地址,可以找到该字符串。
#include <stdio.h>
#include <string.h>
int main(){
const char *p = "abcdef";//这是字符串常量,不能被改变,所以用const修饰人为防止修改。
int len = strlen(p);
printf("%s", p);
return 0;
}
注意:一个字符指针只能存放一个地址,存不下整个字符串。
#include <stdio.h>
int main(){
char* p1 = "abcdef";
char* p2 = "abcdef";
if(p1 == p2){
printf("p1 == p2\n");
}
else{
printf("p1 != p2\n");
}
char str1[] = "abcdef";
char str2[] = "abcdef";
if(str1 == str2){
printf("str1 == str2\n");
}
else{
printf("str1 != str2\n");
}
return 0;
}
运行结果:
结果分析:
"abcdef"
是字符串常量,创建之后就不能够改变,其在内存中是唯一的一份,字符指针p1与p2均指向了字符串常量的首元素,二者相等。 字符数组则有些不同,str1[[]与str2[]是两个不同的数组,二者在内存空间中占据着不同的位置,数组名是数组首元素的地址,str1与str2是不同的数组名,二者不同。
是存放指针的数组。 重点在后头。
#include <stdio.h>
int main(){
//(一级)整型指针数组
int* arr1[10] = {0};
//二级整型指针数组
int** arr2[10] = {0};
//一级字符指针数组
char* ch1[10] = {0};
//二级字符指针数组
char** ch2[10] = {0};
return 0;
}
#include <stdio.h>
int main() {
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 2,3,4,5,6 };
int arr3[5] = { 3,4,5,6,7 };
int* parr[3] = { 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]);//*(parr[i]+j)
}
printf("\n");
}
return 0;
}
运行结果:
arr1、arr2、arr3这三个数组并不一定连续,与二维数组是连续的有着本质不同。
是指向数组的指针,用来存放数组的地址。 重点在后头。
例如:int (*p)[5]
解释:看见小括号,p1小于*结合,故p1首先是一个指针,指向一个数组,该数组有5个元素,每个元素的类型都是int。 p的类型是去掉p之后的部分 ,即
int (*)[5]
。注意:[]的优先级高于*
数组名一般情况下指的是数组首元素的地址。 除了两种特殊情况下指的是整个数组。
举例说明:
#include <stdio.h>
int main(){
int arr[10] = {0};
printf("sizeof(arr[0]) == %d\n", sizeof(arr[0]));
printf("sizeof(arr) == %d\n", sizeof(arr));
//-------------------------------------------------
printf("arr == %p\n", arr);
printf("arr + 1 %p\n", arr + 1);
printf("arr[0] == %p\n", arr[0]);
printf("arr[0] + 1%p\n", arr[0] + 1);
printf("&arr == %p\n", &arr);
printf("&arr + 1%p\n", &arr + 1);
return 0;
}
运行结果:
#include <stdio.h>
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int (*p)[10] = &arr;
int i = 0;
for(i=0; i<10; i++){
printf("%d ", *(*p + i));//
}
return 0;
}
运行结果:
p是整个数组的地址,*p就是整个数组,相当于数组名arr,也就是数组首元素的地址,**p就是数组首元素arr[0]。
正常二维数组使用:
#include <stdio.h>
void prints(int arr[3][4], int row, int col) {
int i = 0;
for (i = 0; i < row; i++) {
int j = 0;
for (j = 0; j < col; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main() {
int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
prints(arr, 3, 4);
return 0;
}
使用数组指针:
#include <stdio.h>
void prints(int (*p)[4], int row, int col) {
int i = 0;
for (i = 0; i < row; i++) {
int j = 0;
for (j = 0; j < col; j++) {
printf("%d ", *(*(p+i)+j));//p[i][j]
}
printf("\n");
}
}
int main() {
int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
prints(arr, 3, 4);
return 0;
}
运行结果:
二维数组的首元素是一维数组。 二维数组在内存中实际上是以一维数组的方式进行储存的。
是存放数组指针的数组。
#include <stdio.h>
int main(){
int arr1[] = {1,2,3,4,5};
int arr2[] = {2,3,4,5,6};
int arr3[] = {3,4,5,6,7};
int (*arr[3])[5] = {&arr1, &arr2, &arr3};
return 0;
}
#include <stdio.h>
void test_1(int arr[10]){
//正确
}
void test_2(int arr[]){
//正确
}
void test_3(int* arr){
//正确
}
int main(){
int arr[10] = {0};
test_1(arr);
test_2(arr);
test_3(arr);
return 0;
}
#include <stdio.h>
void test_1(int* arr[10]){
//正确
}
void test_2(int** arr){
//正确
}
int main(){
int* arr[10] = {0};
test_1(arr);
test_2(arr);
return 0;
}
多维数组作为参数时,只能够省略第一维的数,也就是以一个方框的数。
#include <stdio.h>
void test_1(int arr[3][4]){
//正确
}
void test_2(int arr[][4]){
//正确
}
void test_3(int arr[][]){
//错误!
}
void test_4(int* arr){
//错误!
}
void test_5(int* arr[4]){
//错误!
}
void test_6(int (*arr)[4]){
//正确
}
void test_7(int** arr){
//错误!
}
int main(){
int arr[3][4];
test_1(arr);
test_2(arr);
test_3(arr);
test_4(arr);
test_5(arr);
test_6(arr);
test_7(arr);
return 0;
}
#include <stdio.h>
void test(int *p){
}
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int* p = arr;
test(p);
return 0;
}
当函数参数是一级指针时,传递的数据可以有哪些情况?
void test(int *p){}
可以是一级指针、一维数组的数组名、&变量名、二级指针解引用。
#include <stdio.h>
void test(int** p){
}
int main(){
int a = 10;
int* pa = &a;
int** ppa = &pa;
test(ppa);
test(&pa)
return 0;
}
当函数参数是二级指针时,传递的数据可以有那些情况?
void test(int** p){}
可以是二级指针int **p
、 一级指针的地址、指针数组的数组名int* arr[5]
。
是指向函数的指针,用来存放函数的地址。 一个函数在编译期间就分配了地址。
int
(*p)(int,int);
首先是一个指针p,指向一个函数,这个函数的参数有两个,均是int类型,这个函数的返回类型是int类型。
函数名就是函数的地址,&函数名也是函数的地址,故二者等价。
#include <stdio.h>
int Add(int x, int y){
return x + y;
}
int main(){
printf("%p", &Add);
printf("%p", Add);
return 0;
}
运行结果:
&Arr - 取出了数组的地址 &Add - 取出了函数的地址
#include <stdio.h>
int Add(int x, int y){
return x + y;
}
int main(){
int arr[10] = {0};
int (*p1)[10] = &arr;
//
int (*p)(int,int) = &Add;//int (*p)(int, int) = Add
return 0;
}
#include <stdio.h>
int Add(int x, int y) {
return x + y;
}
int main() {
int (*p)(int, int) = &Add;//int (*P)(int, int) = Add
int x = 3;
int y = 4;
//直接函数调用
int ret1 = Add(x, y);
//通过函数指针的函数调用
int ret2 = (*p)(x, y);
int ret3 = p(x, y);//由函数直接调用知,其直接使用的是函数名-也就是函数的地址,而p中存放的就是函数的地址,故p与函数名等价
//由此,*在此处函数调用下不起作用,可以没有,也可以有多个。
int ret4 = (*****p)(x, y);
int ret5 = (*Add)(x, y);
printf("ret1 == %d\n", ret1);
printf("ret2 == %d\n", ret2);
printf("ret3 == %d\n", ret3);
printf("ret4 == %d\n", ret4);
printf("ret5 == %d\n", ret5);
return 0;
}
运行结果:
(* ( void (*)() ) 0)();
这是一次函数调用,调用了0地址处的函数。 整数0被一个函数指针void (*)()
强制类型转换为该函数指针类型,之后解引用间接对0地址处的函数进行调用。 其中这个函数的没有形参,返回类型为void。
void (* signal( int , void (*)(int) ) )(int);
这是一个函数声明。 函数名是signal
,形参一个是int;另一个是函数指针void (*)(int)
,该函数指针指向一个形参为int
,返回类型为void
的函数。 返回类型又是一个函数指针void (*)(int)
,该函数指针指向一个形参为int
,返回类型为void
的函数。
使用typedef
对函数指针void (*)(int)
类型进行重命名,简化上面的函数声明:
//对void (*)(int)类型重命名为pf
typedef void (* pf)(int);
//对void (* signal( int , void (*)(int) ) )(int)进行化简
pf signal(int, pf);
实现一个简单的计算器
#include <stdio.h>
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, y;
int ret = 0;
do {
menu();
printf("请输入你的选择>");
scanf("%d", &input);
switch (input) {
case 1:
printf("请输入两个操作数>");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个操作数>");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数>");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数>");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("程序退出\n");
break;
default:
printf("输入错误,请重新输入!\n");
break;
}
} while (input);
return 0;
}
注意到switch语句内部case1到case4的代码太相似,显得代码有些臃肿。
把这些相似的代码封装成一个函数,用函数指针来接收,达到简化代码的目的。
#include <stdio.h>
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;//除法
}
//封装的一个函数
void calculate(int (*p)(int, int)){
printf("请输入两个操作数>");
int x,y;
scanf("%d %d", &x, &y);
int ret = p(x, y);
printf("%d\n", ret);
}
int main() {
int input = 0;
do {
menu();
printf("请输入你的选择>");
scanf("%d", &input);
switch (input) {
case 1:
calculate(Add);
break;
case 2:
calculate(Sub);
break;
case 3:
calculate(Mul);
break;
case 4:
calculate(Div);
break;
case 0:
printf("程序退出\n");
break;
default:
printf("输入错误,请重新输入!\n");
break;
}
} while (input);
return 0;
}
是存放函数指针的数组。
int (* arr[10])(int, int);
其中arr
是数组名。 解释:arr先与[10]结合,是一个数组,这个数组有10个元素,每一个元素都是一个函数指针。 这个函数指针类型是int (*)(int, int)
,指向一个有两个int类型
形参,返回类型是int类型
的函数。
使用函数指针数组存放Add( )、Sub( )、Mul( )、Div( )函数,不再使用switch语句。
#include <stdio.h>
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 (* arr[10])(int, int) = {0, Add, Sub, Mul, Div};
int x, y;
int ret = 0;
do {
menu();
printf("请输入你的选择>");
scanf("%d", &input);
if(input > 0 && input < 5){
printf("请输入两个操作数>");
scanf("%d %d", &x, &y);
ret = (arr[input])(x, y);
printf("%d\n", ret);
}
else if(input == 0){
printf("程序退出!\n");
}
else{
printf("输入错误,请重新输入!\n");
}
} while (input);
return 0;
}
是一个指针。 指向一个数组。 数组的元素都是函数指针,也就是函数的地址。
函数指针数组:
int (*arr[10])(int, int) = {Add, Sub};
其中Add、Sub是函数名。 指向函数指针数组的指针:int (* (*parr)[10])(int, int) = &arr;
回调函数是一个通过函数指针调用的函数。 当你把函数的指针(或地址)作为参数传递给另一个函数,这个指针被用来调用其所指向的函数时,就说这是回调函数。 回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,对该事件或条件进行反馈。
void qsort(void* base, //待排序的数据的起始地址
size_t num, //待排序的数据元素个数
size_t size, //待排序的单个数据元素的大小(以字节为单位)
int (*comper)(const void* e1, const void* e2));
//函数指针,指向一个比较函数,该函数的形参是const void* e1和const void* e1,返回 //类型是int。
qsort函数可以用来比较任意相同类型的数据,具体比较哪一种类型的数据由使用者自己决定。 决定的方式便通过比较函数实现,这个比较函数需要由使用者写出。 这个比较函数可以接收两个地址,并返回一个整数。
void*
类型的指针作为形参可以接收任意类型的指针,是一种泛型指针,也可以说是垃圾箱-来者不拒。 比较函数内部,对e1与e1所指向的数据进行比较之前需要先进行强制类型转换,否则出错。而强制类型转换为哪一种类型由待比较的数据的类型决定。qsort函数排序默认结果是升序排列。 默认返回情况如下: e1所指向数据大于e2所指向数据返回1; e1所指向数据等于e2所指向数据返回0; e1所指向数据小于e2所指向数据返回-1。
void*
是无具体类型的指针,可以接收任意类型的地址。void*
类型的指针直接进行解引用操作、等指针运算操作。
qsort
是基于快速排序思想写成的函数,头文件是stdlib.h
对整型数据进行排列:
#include <stdio.h>
int cmp_int(const void* e1, const void* e2) {
return *(int*)e1 - *(int*)e2;
}
void test_int() {
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}
printf("\n");
qsort(arr, sz, sizeof(arr[0]), cmp_int);
for (i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
test_int();
return 0;
}
运行结果:
对结构体类型数据进行排列:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct student {
char name[20];
int num;
}STU;
//比较结构体中的字符串
int cmp_name(const void* e1, const void* e2) {
return strcmp(((STU*)e1)->name, ((STU*)e2)->name);
}
void test_struct_name() {
STU s[] = { {"sunwukong", 2}, {"shasheng", 4}, {"tangsheng",1},{"zhubajie",3} };
int sz = sizeof(s) / sizeof(s[0]);
int i = 0;
printf("前\n");
for (i = 0; i < sz; i++) {
printf("%s %d\n", s[i].name, s[i].num);
}
qsort(s, sz, sizeof(s[0]), cmp_name);
printf("后\n");
for (i = 0; i < sz; i++) {
printf("%s %d\n", s[i].name, s[i].num);
}
return 0;
}
//比较结构体中的整型
int cmp_num(const void* e1, const void* e2) {
return ((STU*)e1)->num - ((STU*)e2)->num;
}
void test_struct_num() {
STU s[] = { {"sunwukong", 2}, {"shasheng", 4}, {"tangsheng",1},{"zhubajie",3} };
int sz = sizeof(s) / sizeof(s[0]);
int i = 0;
printf("前\n");
for (i = 0; i < sz; i++) {
printf("%s %d\n", s[i].name, s[i].num);
}
qsort(s, sz, sizeof(s[0]), cmp_num);
printf("后\n");
for (i = 0; i < sz; i++) {
printf("%s %d\n", s[i].name, s[i].num);
}
return 0;
}
int main() {
test_struct_name();
printf("----------------\n");
test_struct_num();
return 0;
}
运行结果:
冒泡排序思想: 快速图解:
慢速图解:
一个例子:
#include <stdio.h>
void bubble_sort(int arr[], int sz) {
int i = 0;
int flag = 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;
//交换过,flag要改变
flag = 1;
}
}
//如果第一趟为交换,那么之后的几趟也不会交换,直接退出!
if(flag == 0){
break;
}
}
}
int main() {
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}
printf("\n");
bubble_sort(arr, sz);
for (i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}
return 0;
}
模拟实现:
void Swap(char* buf1, char* buf2, int size) {
int i = 0;
for (i = 0; i < size; i++) {
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
//
void bubble_sort(void* base,
size_t num,
size_t size,
int (*cmp)(const void* e1, const void* e2)) {
int i = 0;
//趟数
for (i = 0; i < num - 1; i++) {
int j = 0;
int flag = 0;//默认数据已经升序排列,不需要交换
//比较次数
for (j = 0; j < num - 1 - i; j++) {
//比较,
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0) {
//交换
Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
//交换过,flag要改变
flag = 1;
}
}
//如果第一趟为交换,那么之后的几趟也不会交换,直接退出!
if(flag == 0){
break;
}
}
}
注意比较部分:
cmp((char*)base + j * size, (char*)base + (j + 1) * size)
,
作为设计者,应该想到作为一个通用的排序函数,可以比较事先未知的数据类型。既然是未知的数据类型那么就不能仅仅使用某一种特定的数据类型直接对指针解引用找到某一个元素,而是把指针
base
强制类型转换为字符指针,从而借助单个数据元素大小size
找到相应的元素。
注意交换部分:
Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
void Swap(char* buf1, char* buf2, int size) {
int i = 0;
for (i = 0; i < size; i++) {
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
作为设计者,既然实现不能事先知道排序的是哪一种类型,那要实现交换两个元素通过两个字符指针分别指向两个元素,以字节为单位交换这两个元素对应的内容,这样就不再需要考虑是哪一种数据类型,直接完成交换。
本节主要涉及了指针的一些拓展知识,从一般的一级指针开始、逐渐到指针数组、数组指针、函数指针、函数指针数组。另外还了解了回调函数,模拟实现了qsort函数并很好地完成了功能。
END
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有