目录
指针初阶
指针定义
指针和指针类型
c语言的整型指针解引用与整型变量的区别
内存
指针和指针类型
指针类型的意义
野指针
规避野指针
指针运算
指针和数组
二级指针
指针数组
指针进阶
指针的概念再提起
字符指针
《剑指offer》
字符串常量:
指针数组
数组指针
数组名是数组首元素地址
数组传参和指针传参
一维数组传参
二维数组传参
一级指针传参
二级指针传参
加深印象
函数指针
有趣的代码
推荐书籍《C陷阱与缺陷》
函数指针数组
模拟计算器
指向函数指针数组的指针
回调函数
冒泡排序
指针和数组结合的冒泡排序
模仿qsort实现冒泡排序通用算法
快速排序改良
指针初阶内容
指针定义
指针从根本上来看是一个值为内存地址的变量或者数据对象,利用地址,他的值直接指向(points to)存在电脑存储器中另一个地方的值。指针变量的值是地址。例如一个指针的变量名为p,那么我们可以通过 p = &a;将a的地址赋值给p,从而我们可以通过对p进行操作,对a进行修改等操作。地址形象地称为指针。
编号 - 地址 - 指针,they are same
要创建指针变量,首先要先生命指针变量的类型。例如:
int *a = NULL;//指针的类型是int *,指针所指向的是int类型的.
short *ps = NULL;
long *pc = NULL;
char *b = NULL;//指针的类型是char *,指针所指向的是char类型的.
double *c = NULL;//指针的类型是double *,指针所指向的是double类型的.
int **ptr;//指针的类型是int **,指针所指向的是int*类型的.
#include<stdio.h>
int main()
{
int a = 10;//a占4个字节;
int * pa = &a;//拿到的是a的4个字节中第一个字节的地址;
*pa = 20;
return 0;
}
指针是用来存放地址的,地址是唯一标示一块地址空间的。
指针的大小在32位平台是4个字节,在6位平台是8个字节。
指针和指针类型
#include<stdio.h>
int main()
{
int* pa;
char* pc;
float * pf;
printf("%d\n",sizeof(pa));
printf("%d\n",sizeof(pc));
printf("%d\n",sizeof(pf));
return 0;
}
8
8
8
指针类型决定了指针解引用的权限有多大
引入:简单介绍一下整型变量,指针,引用符和解引用符
定义一个整型变量a: int a;
如果使用引用符号&,&a则是整型变量a的地址,也就是一个整型指针。
定义一个整型指针b: int *b;b=&a;(这里把变量a的地址,也就是指针&a赋给了指针b)
如果对一个指针使用解引用符号*,如b,便表示整型指针b存储的地址所指向的整型变量。
指针类型的意义
#include<stdio.h>
int main()
{
int a = 0x11223344;
char *pc = &a;
//0 1 2 3 4 5 6 7 8 9 a b c d e f
//11111111
// 8421
//int *pa = &a;
//*pa = 0;
//printf("%d\n",*pa);
return 0;
}
c语言的整型指针解引用与整型变量的区别
#include<stdio.h>
int main()
{
int a = 0;
a++;
printf("%d\n",a);
int *b;
b = &a;
a=0;
*b++;
printf("%d\n",a);
return 0;
}
输出:1
0
同样开始的时候赋予变量0值,直接对变量本身自增成功了。
但是拿变量的指针的解引用来自增,结果变量并没有改变。
#include<stdio.h>
int main()
{
int a;
int *b;
b = &a;
a=0;
(*b)++;
printf("%d\n",a);
return 0;
}
1
把代码中的指针的整型指针的解引用加上括号后再自增,结果自增成功了。
经过更多的实验,得出结论:内存分布
C中,,和++运算符同时存在时,后者比前者优先级更高,电脑会把*b++解读成 *(b++),即把b存的地址+1,再解应用,实际上解引用得到的不是本来指针所指的整型变量。
总结:在使用整型变量的指针时,(其实不止是对整型变量),要注意运算符的优先级。解引用时要注意解引用的指针是否发生了变化。
内存
内存是电脑上特别重要的存储器,计算机中所有程序的运行都是在内存中进行的。 所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。 为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
0 |
---|
1 |
2 |
3 |
4 |
给每个格子编了号
32位 - 32根地址线 - 物理线 - 通电 - 1/0
64位 - 64根地址线 - 物理线 - 通电 - 1/0
电信号转换成数字信号:0和1组成的二进制序列
00000000000000000000000000000000---内存编号
11111111111111111111111111111111---内存编号
2^32个编号
假设:一个内存单元是1 bit
2^32 bit = 512 MB =0.5 GB
#include<stdio.h>
int main()
{
int a = 10;//a在内存重要分配的空间 - 4个字节
printf("%p\n",&a);
int* pa = &a;//pa是用来存放地址的,在C语言中是指针变量
//* 说明 pa是指针变量
//int 说明pa执行的对象是int类型的
printf("%p\n",pa);
char ch = 'w';
printf("%p\n",ch);
char* pc = &ch;
printf("%p\n",*pc);
return 0;
}
000000000061FE0C
000000000061FE0C
0000000000000077
0000000000000077
就可以输出a的内存地址,比如说我的a的地址就是000000000061FE1C(一串十六进制数)
int main()
{
int a = 10;
int* pa = &a;
*pa = 20;//* 叫做解引用操作 *pa就是通过pa里边的地址,找到a
//就是用*pa赋一个值改变原来的值
printf("%d\n",a);
return 0;
}
输出结果:20
int main()
{
printf("%d\n",sizeof(char*));
printf("%d\n",sizeof(short*));
printf("%d\n",sizeof(int*));
printf("%d\n",sizeof(long*));
printf("%d\n",sizeof(long long*));
return 0;
}
输出:
8
8
8
8
8
指针和指针类型
#include<stdio.h>
int main()
{
int a = 0x11223344;
int*pa = &a;
printf("%p\n",a);
printf("%p",*pa);
*pa = 0;
printf("%p",a);
printf("%p",*pa);
return 0;
}
0000000011223344
0000000011223344
0000000000000000
0000000000000000
char* pc 不能来复制 int型 的地址
#include<stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
//char* pc = arr;//error,此处报错
printf("%p\n",arr);
printf("%p\n",p);
printf("%p\n",p+1);
//printf("%p\n",*pc);
return 0;
}
#include<stdio.h>
int main()
{
int arr[10] = {0};
char ch[10] = {'i'};
int *p = arr;
char* pc = ch;
printf("%p\n",arr);
printf("%p\n",p);
printf("%p\n",p+1);
printf("%p\n",ch);
printf("%p\n",pc);
printf("%p",pc+1);
return 0;
}
000000000061FDE0
000000000061FDE0
000000000061FDE4
000000000061FDD6
000000000061FDD6
000000000061FDD7
指针类型的意义
1.指针类型决定:指针解引用的权限有多大
2.指针类型决定:指针走一步,能走多长(步长)
字符型指针加一,指针加一
整型指针加一,指针加四
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int *p = arr;
for(int i = 0;i < 10; i++)
{
*(p+i) = 1;//p+i 其实是下标为i的地址//而char类型则是一个字节一个字节访问
}
return 0;
}
野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
1.解释:指针指向的位置不可知
2.三个成因:
指针未初始化 指针越界访问 指针指向的空间释放(动态内存开辟)
1.指针未初始化
#include<stdio.h>
int main()
{
//这里的p就是一个野指针
int *p;//p是一个局部的指针变量,局部变量不初始化的话,默认是随机值
*p = 20;//非法访问内存
return 0;
}
2.指针越界
#include<stdio.h>
int main()
{
int arr[10] = {0};
int* p = arr;
int i = 0;
for(i = 0;i <= 10; i++)
{
*p = i;
p++;//当i= 10,时,造成的第十一位出现,造成越界
}
return 0;
}
3.指针指向的空间释放(动态内存开辟)
#include<stdio.h>
int* test()
{
int a = 10;
return &a;//使用后生命周期结束,归还操作系统
}
int main()
{
int* p = test();
*p = 20;//非法访问内存
return 0;
}
局部变量a出函数后销毁,本属于a的空间不是当前程序的,已经还给操作系统,可能会被分配储存其他内容
访问一块已经被释放的空间,则会产生野指针
像这种访问临时变量的地址,都存在问题
除非出了范围不销毁,比如初始 static 变量
规避野指针
1.指针初始化
2.小心指针越界
3.指针指向空间释放,及时置NULL
4.指针使用之前检查有效性(NULL不能访问)
#include<stdio.h>
int main()
{
//当前不知道p应该初始化为何地址的时候,直接初始化为NULL
int* p = NULL;
//明确知道初始化的值
int a = 10;
int* ptr = &a;
//C语言本身是不会检查数据的越界行为的。
int* p1 = NULL;
if(p != NULL)
*p1 = 10;
return 0;
}
指针运算
·指针+-整数 ·指针-指针 ·指针的关系运算
1.指针+-整数
#include<stdio.h>
#define N_VALUES 5
int main()
{
float values[N_VALUES];
float *vp;
//指针+-整数:指针的关系运算
for(vp = &values[0];vp < &values[N_VALUES];)
{
*vp++ = 0;
}//成功循环一遍
return 0;
}
相应的,可以倒着进行循环。
#include<stdio.h>
#define N_VALUES 5
int main()
{
float values[N_VALUES];
float *vp;
//指针+-整数:指针的关系运算
for(vp = &values[N_VALUES - 1];vp >= &values[0];)
{
*--vp = 0;
}
return 0;
}
#include<stdio.h>
#define N_VALUES 5
int main()
{
float values[N_VALUES];
float *vp;
//指针+-整数:指针的关系运算
for(vp = &values[N_VALUES - 1];vp >= &values[0];vp--)
{
*vp = 0;
}
return 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
2.指针-指针
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n",&arr[9] - &arr[0]);//指针-指针得到的是两个指针之间的元素个数
return 0;
}
9
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
char c[5];
printf("%d\n",&arr[9] - &c[0]);//error
return 0;
}
//虽然可能有值算出,但是因为指针类型不同,所以是无效的
指针和指针相减的前提是两个指针指向同一块空间。
3.指针的关系运算
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int *pend = arr + 9;
while(p <= pend)
{
printf("%d\n",*p);
p++;
}
return 0;
}
4.strlen函数再回顾
4.1.库函数法
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = {};
scanf("%s",&arr);
//比如说['a']['b']['c']['d']
printf("%d\n",strlen(arr));
return 0;
}//直接调用库函数
4.2.函数计数器法
#include<stdio.h>
#include<string.h>
int my_strlen(char* str)
{
int count = 0;
while(*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = {};
scanf("%s",&arr);
printf("%d\n",my_strlen(arr));
return 0;
}//计数器方法
4.3.递归法
#include<stdio.h>
int my_strlen(char* str)
{
if(*str != '\0')
{
return 1 + my_strlen(str + 1 );
}
else return 0;
}
int main()
{
char arr[] = {};
scanf("%s",&arr);
printf("%d\n",my_strlen(arr));
return 0;
}//递归算法
4.4.指针-指针法
#include<stdio.h>
int my_strlen(char* str)
{
char* start = str;
while(*str != '\0')
{
str++;
}
return str - start;
}
int main()
{
char arr[] = {};
scanf("%s",&arr);
printf("%d\n",my_strlen(arr));
return 0;
}
指针和数组
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%p\n",arr);//数组名是数组首元素的首地址
printf("%p\n",&arr[0]);
printf("%p",&arr[1]);
return 0;
}
000000000061FDF0
000000000061FDF0
000000000061FDF4
数组遍历
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
for(int i = 0;i < 10; i++)
{
printf("%p <==> %p\n",&arr[i],p + i);
}
return 0;
}
000000000061FDE0 <==> 000000000061FDE0
000000000061FDE4 <==> 000000000061FDE4
000000000061FDE8 <==> 000000000061FDE8
000000000061FDEC <==> 000000000061FDEC
000000000061FDF0 <==> 000000000061FDF0
000000000061FDF4 <==> 000000000061FDF4
000000000061FDF8 <==> 000000000061FDF8
000000000061FDFC <==> 000000000061FDFC
000000000061FE00 <==> 000000000061FE00
000000000061FE04 <==> 000000000061FE04
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
for(int i = 0;i < 10; i++)
{
*(p + i) = i;
}
for(int i = 0;i < 10; i++)
{
printf("%d ",*(p+i));
}
return 0;
}
0 1 2 3 4 5 6 7 8 9
当然也可以通过如下方式进行操作
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;//数组名
printf("%d\n",arr[2]);
printf("%d\n",p[2]);//p[2]-->*(p + 2)
printf("%d\n",*(p+2));
printf("%d\n",2[arr]);//其效果实际上就是arr[2]-->*(arr + 2)
return 0;
}
3
3
3
3
二级指针
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;//pa是指针变量,一级指针
//ppa就是一个二级指针变量
int** ppa = &pa;//pa也是个变量,&pa取出pa在内存中的起始地址
int*** pppa = &ppa;
printf("%p\n",a);
printf("%p\n",*pa);
printf("%p\n",**ppa);
printf("%p\n",***pppa);
return 0;
}
000000000000000A
000000000000000A
000000000000000A
000000000000000A
指针数组
指针数组本质上是数组
#include<stdio.h>
int main()
{
int arr[10];//整形数组 - 存放整型的数组就是整型数组
char ch[5];//字符数组 - 存放字符的数组就是字符数组
//指针数组 - 存放指针的数组
int* parr[5];//整型指针的数组
char* pch[5];//字符指针的数组
return 0;
}
代码三种境界:
1.看代码就是代码
2.看代码就是内存
3.看代码还是代码
指针进阶
指针的概念再提起
1.指针就是一个变量,用来存放地址,地址唯一标识一块内存空间。
2.指针的大小是固定的4/8个字节(32位平台/64位平台)。
3.指针是有类型的,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4.指针的运算。
指针:通过地址能找到所需的变量单元,因此说,地址指向该变量单元,将地址形象化的称为指针;可理解为地址就是指针; 指针变量:存放指针(地址)的变量; 指针的作用:通过指针间接访问内存; 指针变量的定义:类型名 * 指针变量名 一个变量的指针的含义包含两个方面,一是存储单元的纯地址,二是指向存储单元的数据类型,故定义指针变量时要指定基类型,用来指定此指针变量可以指向的变量的类型; 注释:* 取内容运算符,间接访问运算符 ,*p代表指针变量p所指向的对象;& 取地址运算符,&a是变量a的地址;
# include <stdio.h>
int main()
{
int a;
a = 10;
int *p;
p = &a;
printf("a address is %p\n",p);//这里得到的地址是0x0060ff00,不同编译器得到的地址不一样
int *p2 = (int *)0x0060ff00;//将指针变量p2指向这块内存地址
*p2 =20;
printf("在内存的%p的位置,存放值是%d\n",p2,*p2);
}//打印输出:在内存的0x0060ff00的位置,存放值是20
字符指针
#include <stdio.h>
int main()
{
char ch = 'q';
char* pc = &ch;
*pc = 'p';
printf("%c",ch);
return 0;
}
输出结果:h
或者用下面的方式
#include<stdio.h>
int main()
{
char* ps = "hello bit.";
char arr[] = "hello bit.";
printf("%s\n", ps);
printf("%s\n", arr);
printf("%p\n",ps);
printf("%p\n",arr);
return 0;
}
输出结果:hello bit.
hello bit.
0000000000404000
000000000061FE0D//地址实际上不相同的
1.习题-判断字符串是否相等
#include<stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
char* str3 = "hello bit.";
char* str4 = "hello bit.";
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 and str2 are not same
str3 and str4 are same
题目解释:str1[]、str2[]是两个数组,地址是不一样的;
str3、str4是两个指针变量,两个里面放的都是同一个h(常量字符串不能改,所以两者共用一份字符 串,所以是一样的)
《剑指offer》
#include<stdio.h>
int main()
{
char*ps = "hello bit.";
char arr[] = "hello bit.";
printf("%s\n", ps);
printf("%s\n", arr);
return 0;
}
代码无法输出 err
常量字符串是不能改的
#include<stdio.h>
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");
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;
}
const char常量字符串
字符串常量:
字符常量: 由一对单引号括起来的单个字符,如'a', 'B';
字符串常量: 由一对双引号括起来的多个字符的序列,如"a",“I Love u”;
字符串常量: 由一对双引号括起来的多个字符的序列,如"a",“I Love u”
字符串常量是不能改的,所以只需要存储一份即可。
指针数组
#include<stdio.h>
int main()
{
//指针数组
//数组 - 数组中存放的是指针(地址)
int* arr[4];
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\n",*arr[i]);
}
return 0;
}
#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};//arr[]数组又涵盖了上面三个数组;把a,b,c三个数组的首元素地址放入arr[]中去了。
int i = 0;
for(i = 0;i < 3; i++)
{
int j = 0;
for(j = 0;j < 5;j++)
{
printf("%d ",*(arr[i] + j));
}
printf("\n");
}
return 0;
}
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
#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("\n");
}
return 0;
}
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
arr[i] [j]和arr[i]+j等价,模拟了二维数组,但不是真正的二维数组,因为二维数组是连续的
arr |
---|
int* 1 2 3 4 5 ——a |
int* 2 3 4 5 6 ——b |
int* 3 4 5 6 7 ——c |
数组指针
数组指针是一种指针。
#include<stdio.h>
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 };
//arr - 数组名是首元素的地址 - arr[0]的地址
int (*parr)[10] = &arr;//取出整个数组的地址
//parr就是一个数组指针
double* d[5];
double* (*pd)[5] = &d;//pd就是一个数组指针
return 0;
}
1.指针数组和数组指针之间的区别
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int* p1 = arr;
int (*p2)[10] = &arr;
printf("%p\n",arr);
printf("%p\n",&arr);//两者指向同一个地址,但是两者类型不同
printf("%p\n",p1);
printf("%p\n",p1+1);
printf("%p\n",p2);
printf("%p\n",p2+1);
return 0;
}
000000000061FDE0
000000000061FDE0
000000000061FDE0
000000000061FDE4
000000000061FDE0
000000000061FE08
类似与下面的例子:
char c = 'a';//97 int c = 97;//两者值是一样的,但是类型不同
数组名是数组首元素地址
但是有两个例外
1. sizeof(数组名) - 数组名表示整个数组,计算的是整个数组的大小。单位是字节
2.&数组名 - 数组名 表示整个数组,取出来的整个数组的地址
2.一维数组的数组指针应用
#include<stdio.h>
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };//实际上一维数组不适合取数组指针
//int* pa = arr;
//for(int i = 0;i < 10; i++)
//{
//printf("%d ",*pa+i);
//}
int (*pa)[10] = &arr;
for(int i = 0;i < 10; i++)
{
printf("%d ",*((*pa) + i));//这样写实际上是非常别扭的
}
return 0;
}
3.二维数组的数组指针应用
原先的写法:
#include<stdio.h>
void print(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");
}
}
int main()
{
int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
print(arr, 3, 5);
return 0;
}
数组指针写法:
#include<stdio.h>
void print(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");
}
}
int main()
{ arr[0] arr[1] arr[2]
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
print(arr, 3, 5);//arr数组名,传参数组的首元素地址
return 0;
}
//二维数组的数组名表示首元素的地址
//二维数组的首元素是:第一行
int arr[10];//整型数组 int *parr1[10];//整型指针的数组 int (*parr2)[10];//整型数组指针,该指针能够指向一个数组,数组10个元素,每个元素的类型是int int (*parr3[10])[5];//整型数组指针的数组 parr3是一个存放数组指针的数组 该数组能够存放10个数组指针,每个数组指针能够指向一个数组,数组5个元素,每个元素是int类型
数组传参和指针传参
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)//对的
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};//存放int*的数组
test(arr);
test2(arr2);
return 0;
}
2.二维数组传参
#include<stdio.h>
void test(int arr[3][5])
{}
void test(int arr[][5])
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字
//因为对一个二维数组而言,可以不知道有多少行,但必须知道一行有几个元素
//这样才方便运算
void test(int (*parr)[5])
{}
int main()
{
int arr[3][5];
test(arr);
return 0;
}
3.一级指针传参
#include<stdio.h>
void print(int* ptr,int sz)
{
for(int i = 0;i < sz; i++)
{
printf("%d ",*(ptr + i));
}
}
void test(char* p)
{
printf("%c",*p);
}
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]);
//p是一级指针
print(p,sz);
char ch = 'w';
char* pch = &ch;
test(&ch);
test(pch);
return 0;
}
4.二级指针传参
#include<stdio.h>
void test(int** ptr)
{
**ptr = 20;
}
int main()
{
int a = 10;
int* pa = &a;//pa是一级指针
int** ppa = &pa;//ppa是二级指针
//把二级指针进行传参
test(ppa);
test(&pa);//传一级指针变量的地址
int* arr[10] = {0};
test(arr);//传存放一级指针的数组
printf("%d\n",a);//输出20,
return 0;
}
20
加深印象
一级指针:int* p;——整型指针,指向整型的指针 char* pc;——字符指针,指向字符的指针 void* pv;—— 无类型指针
二级指针:char** p; int** p;
数组指针:指向数组的指针 —— int(*p)[4];
数组:
一维数组 二维数组 指针数组——存放指针的数组
函数指针
函数指针:指向函数的指针,存放函数地址的指针
#include<stdio.h>
int Add(int x,int y)
{
return x + y;
}
int main()
{
int a = 10;
int* pa = &a;
char ch = 'w';
char* pc = &ch;
int arr[10] = {0};
int (*parr)[10] = &arr;//取出数组的地址
//parr是指向数组的指针 - 存放的是数组的地址
//函数指针 - 存放函数地址的指针
//&函数名 - 取到的就是函数的地址
//pf就是一个函数指针变量
int (*pf)(int,int) = &Add;
printf("%p",&Add);
printf("%p",Add);//两者相等
return 0;
}
0000000000401550
0000000000401550
数组名 != &数组名,这两者不等价。数组名是数组首元素的地址,&数组名则是数组所有元素的地址。
函数名 == &函数名,这两者都是函数的地址名
#include<stdio.h>
void test(char* str)
{}
int main()
{
void(*pt)(char*) = &test;
return 0;
}
函数指针传参
#include<stdio.h>
int Add(int x,int y)
{
return x + y;
}
int main()
{
//函数指针 - 存放函数地址的指针
//&函数名 - 取到的就是函数的地址
//pf就是一个函数指针变量
int (*pf)(int,int) = &Add;//int (*pf)(int,int) = Add;这样也是可以的
//也就是说Add等价于pf
//int ret = pf(3,5);等价于int ret = Add(3,5);这么写也是对的
int ret = (*pf)(3,5);
//int ret = *pf(3,5);这样是不行的
printf("%d\n",ret);
return 0;
}
8//说明程序正确
int ret = pf(3,5); int ret = Add(3,5); int ret = (*pf)(3,5);
以上三种写法都是等价的,正确的
int ret = *pf(3,5);这个是例外,是错误的
有趣的代码
//代码2 ((*(void)(*)())0)(); //void(*)() - 函数指针类型 //(void(*)())0 - 对0进行强制类型转换,被解释为一个函数地址 //*(void(*)())0 - 对0地址处进行解引用操作 //(*(void(*)())0)() - 调用0地址处函数 //代码2 void (*signal(int ,void(*)(int)))(int); //函数指针类型嵌套了一个函数指针类型 //signal和()先结合,说明signal是函数名 //signal函数的第一个参数类型是int,第二个参数类型是函数指针 //该函数指针,指向一个参数为int,返回类型是void的函数 //signal是一个函数的声明
代码1: 0前面的是一个函数指针,0被强制转换成函数指针类型。实质是调用0地址处放的函数。该函数无参,返回类型是void。
代码2:可以理解为void (*) (int) signal(int ,void ( *)(int));//但是代码不允许这么写,但是可以这么理解
typedef —— 对类型进行重定义、重命名
typedef void(* pfun_t)(int); //对void(*)(int)的函数指针类型重命名为pfun_t
//typedef unsigned int uint;
pfun_t signal(int, pfun_t);
推荐书籍《C陷阱与缺陷》
函数指针数组
整型指针数组 int* arr[5];
函数指针数组
#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就是函数指针数组
return 0;
}
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;
//计算器-计算整型变量的加减乘除
do
{
menu();
int x;
int y;
int ret = 0;
printf("请选择:>");
scanf("%d",&input);
switch(input)
{
case 1:
printf("请输入两个操作数:>\n");
scanf("%d %d",&x,&y);
printf("ret = %d\n",ret);
ret = Add(x,y);
break;
case 2:
printf("请输入两个操作数:>\n");
scanf("%d %d",&x,&y);
printf("ret = %d\n",ret);
ret = Sub(x,y);
break;
case 3:
printf("请输入两个操作数:>\n");
scanf("%d %d",&x,&y);
printf("ret = %d\n",ret);
ret = Mul(x,y);
break;
case 4:
printf("请输入两个操作数:>\n");
scanf("%d %d",&x,&y);
printf("ret = %d\n",ret);
ret = Div(x,y);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误,重新选择");
break;
}
}while(input);
int (*pfArr[2])(int,int) = {Add, Sub};//pfArr就是函数指针数组
return 0;
}
这时候可以用函数指针来进行模拟计算器
#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;
//计算器-计算整型变量的加减乘除
do
{
menu();
//pfArr就是函数指针数组
//转移表 - 《C和指针》
int (*pfArr[5])(int ,int) = {NULL,Add,Sub,Mul,Div};
int x = 0;
int y = 0;
int ret = 0;
printf("请选择:>");
scanf("%d",&input);
if(input >= 1&&input <= 4)
{
printf("请输入两个操作数:>\n");
scanf("%d %d",&x,&y);
ret = (pfArr[input])(x,y);
printf("ret = %d",ret);
}
else if(input == 0)
{
printf("退出程序\n");
break;
}
else
{
printf("选择错误,请重新选择\n");
}
}while(input);
return 0;
}
指向函数指针数组的指针
函数指针数组本质是数组,取出的是函数指针数组的地址。
整型指针的数组
int* arr[5];
int* (*p2)[5] = &arr;
//p2是指向整型指针数组的指针
函数指针数组 &函数指针数组
int(*p)(int,int);//函数指针
int(* p2[4])(int,int);//函数指针数组
int(* (*p3)[4])(int,int) = &p2;//取出的是函数指针数组的地址
//p3就是一个指向函数指针数组的指针
理解以下的代码
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)[10])(const char*) = &pfunArr;
return 0;
}
以整型数组来类比理解
#include<stdio.h>
int main()
{
int arr[10];
//数组元素类型 - int
//arr数组的类型是 int[10]
return 0;
}
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
1.冒泡排序
首先回顾一下之前的知识,我们熟悉的冒泡排序
#include<stdio.h>
void bubble_sort(int arr[],int sz)
{
int t = 0;
for(int i = 0;i < sz - 1; i++)
{
for(int j = 0;j < sz - 1 - i; j++)
{
if(arr[j] > arr[j+1])
{
t = arr[j];
arr[j] = arr[j+1];
arr[j+1] = t;
}
}
}
}
void print_arr(int arr[],int sz)
{
for(int i = 0;i < sz; i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
print_arr(arr,sz);
bubble_sort(arr,sz);
print_arr(arr,sz);
return 0;
}
2.指针和数组结合的冒泡排序
#include<stdio.h>
#define N 100
int inputarr (int a[]);
void bubble_sort(int *arr, int length);
void outputarr (int *p,int n);
void swap(int *a,int *b);
int main()
{
int a[N];
int n=inputarr(a);
bubble_sort(a, n);
outputarr (a, n);
return 0;
}
int inputarr (int a[])
{
int n;
int i = 0;
while(scanf("%d",&a[i]))
{
if(a[i]==0)
break;
i++;
}
n = i;
return n;
}
void bubble_sort(int *arr, int n)
{ for(int i=0;i<n-1;i++)
{
for(int j=0;j<n-1-i;j++){
if(arr[j]>arr[j+1])
{
swap(&arr[j+1],&arr[j]);
}
}
}
}
void outputarr (int a[],int n)
{ int i;
for(i=0;i<n;i++){
printf("%d ",a[i]);
}
}
void swap(int *a,int *b)
{
int temp;
temp=*a;
*a=*b;
*b=temp;
}
3.模仿qsort实现冒泡排序通用算法
#include<stdio.h>
void bubble_sort(void* base,int sz,int width,int(*cmp)(const void*e1,const void*e2))
{
for(int i = 0;i < sz - 1; i++)
{
for(int 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 Swap(char* buf1,char* buf2,int width)
{
for(int i = 0;i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void print_arr(arr[],sz)
{
for(int i = 0;i < sz; i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
int cmp_int(const void * p1,const void * p2)
{
return (*(int *)p1 - *(int *)p2);//强制转换
}
void test()
{
int arr[] = { 1,3,5,7,9,2,4,6,8,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);
print_arr(arr,sz);
}
int main()
{
test();
return 0;
}
(char* )base + j * width//强制转换类型成char类型,从而指定相应的位置
之所以用char,原因是char类型占字节较小,比较好判断
4.快速排序改良
qsort函数适用于整型数据、字符串数据、结构体数据。而上面的冒泡函数只能排序规定好的数据。
演示一下qsort函数的使用:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
//qsort函数的使用者得实现一个比较函数
//void qsort(void* base,size_t num,size_t size,int (*compar)(const void*,const void*))
//base中存放的是待排序数据中第一个对象的地址
//size_t num元素的个数
//size_t size一个元素的大小
//int (*compar)(const void*,const void*)用来比较两待排序数据中的2个元素的函数
void printf_arr(int arr[],int sz)
{
for(int i = 0;i < sz; i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
int cmp_int(const void * p1,const void * p2)
{
return (*(int *)p1 - *(int *)p2);//强制转换
}
void test1()
{
//整型数据的排序
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
//排序
qsort(arr,sz,sizeof(arr[0]),cmp_int);
//打印
print_arr(arr,sz);
}
struct Stu
{
char name[20];
int age;
};
int sort_by_age(const void* e1,const void* e2)
{
return ((struct Stu*)e1) ->age - ((struct Stu*)e2) ->age;//升序 降序只要把e1 e2位置交换
}
int sort_by_name(const void* e1,const void* e2)
{
return strcmp(((struct Stu*)e1)->name , ((struct Stu*)e2)->name);//strcmp是比较内容的
}
void test2()
{
//使用qsort函数排序结构体数据
struct Stu s[] = {{"zhangsan",30},{"lisi",34},{"wangwu",20}};
int sz = sizeof(s) / sizeof(s[0]);
//按照年龄来排序
qsort(s,sz,sizeof(s[0]),sort_by_age);
//按照名字来排序
qsort(s,sz,sizeof(s[0]),sort_by_name);
}
int main()
{
//test1();
test2();
return 0;
}
A函数 B函数(A函数的地址)(B函数形参部分是A函数指针)
5.改造计算器
#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");
}
void Calc(int(*pf)(int,int))
{
int x = 0;
int y = 0;
printf("请输入两个操作数:>\n");
scanf("%d %d",&x,&y);
return pf(x,y);
}
int main()
{
int input = 0;
//计算器-计算整型变量的加减乘除
do
{
menu();
int x;
int y;
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("选择错误,重新选择");
break;
}
}while(input);
return 0;
}
刚入手学习C语言,希望大家能关注一下我~~~~
如果喜欢这篇文章的话,请您给个赞,也欢迎收藏啊啊啊~~~
同时如果有不对的地方,请您在下面对话框里留言,我会及时改正的,谢谢。