指针复习和练习
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一、指针的基本知识
1.指针的理解
内存中最小的内存单元是一个字节(为了方便char是一个字节,int四个字节,如果最小的是bit(创建的地址太多)或者kb(空间浪费),所以byte最合适)
1kb = 1024byte
每个字节都有自己的一个地址,
实际上取出的一个变量地址是它的起始地址
指针本质上就是地址
指针变量是用来存放地址的变量
总结:
指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
指针的大小在32位平台是4个字节,在64位平台是8个字节
2.指针类型
-
指针±整数
总结:指针的类型决定了指针向前或者向后走一步有多大(距离) -
指针的解引用
总结:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节
2.野指针
1 概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
- 指针未初始化
- 指针越界访问
- 指针指向的空间释放
这里放在动态内存开辟的时候讲解,这里可以简单提示
局部变量已经销毁了,把空间内存还给了操作系统,这快空间已经不属于我们的程序了,但是空间还在,只是我们没有使用权限,但是主函数里面的指针p,仍然记忆着这一块地址,但是我们不能去使用它
如果这块空间没有被别人使用或者覆盖,打印出来的可能仍是原来的值,压栈
2 如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放,及时置NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
零地址是不能访问的
*vp++ :先解引用,地址再++
(*vp)++:解引用后的值++
&地址和*解引用,还没访问呢,随便&好吧,没用就不会造成越界访问了
3.指针运算
-
指针±整数:指针的移动
-
指针-指针 的绝对值得到的是指针和指针之间的元素个数,一样也存在正负(前提是,指向的是用一块空间的两个指针)
没有加法,地址相加是没有意义的
计算字符串长度的大小
- 指针的关系运算
比较大小
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证
它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与
指向第一个元素之前的那个内存位置的指针进行比较。
4.指针和数组
5.二级指针
是用来存放一级指针变量的地址
6.指针数组
存放指针的数组
二、指针的高阶内容
1.字符指针
const char * pc = "abcdef";
//指向的首字符的地址(pc指针里面存放的是首字符a的地址)
//字符串是常量,是不能被改变的,可以用const修饰
常量字符串:只能读不能改,内存只会存一块,两个指针指向了同一块空间同一地址
而数组则是创建了一块新的内存空间来存储字符串,新的空间所以地址肯定不同,但是内容相同(strcmp)
#include <stdio.h>
int main()
{
char str1[] = "offer";
char str2[] = "offer";
const char* str3 = "offer";
const char* str4 = "offer";
//用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。
//如果用strcmp函数来比较字符串的内容,它们是相同的
if (str1 == str2)
printf("str1== str2 \n");
else
printf("str1!=str2 \n");
//这里str3和str4指向的是一个同一个常量字符串。C / C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。
if (str3 == str4)
printf("str3 ==str4\n");
else
printf("str3 !=str4\n");
return 0;
}
运行结果为:
str1!=str2
str3 ==str4
2.指针数组
指针数组是一个存放指针的数组
char **arr3[5];//二级字符指针的数组
//解释:arr3先和[]结合,所以arr3是一个数组,数组里面有5个元素,每个元素的类型是char**
模拟二维数组:指针数组管理多个指针39.40
本质上不是二维数组(二维数组是内存中连续存放的)
3.数组指针
3.1 存放(指向)数组的指针
int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指针指向的是一个数组,数组有10个元素,每个元素的类型是int 。所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
3.2 &数组名VS数组名
去掉p2剩下的就是它的类型,去掉名字就是类型
取出的是整个数组,存放数组的指针
数组指针中的数组元素个数不能省略
11.40别扭的使用方法,错误示范(多此一举)
12.20
至少用在二维数组
二维数组的数组名就是一维数组的地址,
两种方式,内存差不多没节省
4.数组参数、指针参数
4.1 一维数组传参
4.2 二维数组传参
4.3 一级指针传参
反过来思考,通过形参来想,要传什么参数过去
4.4 二级指针传参
5.函数指针
类比数组指针
函数也是有地址的
&函数名
函数名
*可以省略,写多个也可以摆设而已,代码的可读性
Add也可以加个*,但要放在括号里面,否则函数先和括号结合了
函数名就是函数的地址,函数的地址解引用就是函数,没差啦其实
只要写了函数,函数在内存中就有地址了,并不是调用了才有。和全局变量一个道理
放在只读代码区
计算器的最低级的代码写法
//低级计算器代码
#include<stdio.h>
void menu()
{
printf("==================================\n");
printf("===========0退出 1.Add ==========\n");
printf("===========2.Sub 3.Mul ==========\n");
printf("===========4.Div ==========\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;
do
{
menu();
printf("请选择你的操作input: \n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数\n");
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;
}
函数指针改进计算器
//函数指针的写法
#include<stdio.h>
void menu()
{
printf("==================================\n");
printf("===========0退出 1.Add ==========\n");
printf("===========2.Sub 3.Mul ==========\n");
printf("===========4.Div ==========\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 calc(int (*pf)(int , int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择你的操作input: \n");
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;
}
函数指针的用途
简易四则运算的计算器的改进(回调函数,通过函数指针回头再找到它所指向的函数)
看能不能通过函数指针数组完成试一下
函数指针数组
函数指针数组改进计算器
//函数指针数组
#include<stdio.h>
void menu()
{
printf("==================================\n");
printf("===========0退出 1.Add ==========\n");
printf("===========2.Sub 3.Mul ==========\n");
printf("===========4.Div ==========\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;
do
{
menu();
printf("请选择你的操作\n");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
}
else if (input >=1 && input <= 4)
{
int (*pfArr[5])(int, int) = { 0, Add, Sub, Mul, Div };//int (* )(int , int)数组中每个元素的类型都是函数指针
//0也是函数指针吗,反正怎么弄他都是一个零
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
ret = pfArr[input](x, y);
printf("%d\n", ret);
}
else {
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
函数指针和函数指针数组结合改进计算器
//函数指针和函数指针数组结合
#define N 5
#include<stdio.h>
void menu()
{
printf("==================================\n");
printf("===========0退出 1.Add ==========\n");
printf("===========2.Sub 3.Mul ==========\n");
printf("===========4.Div ==========\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 calc(int (*pf)(int , int ))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
int (*pfArr[])(int, int) = { 0, Add, Sub, Mul, Div };
printf("请选择你的操作\n");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
}
else if (input >= 1 && input < N)
{
calc(pfArr[input]);
}
else {
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
如果我后期还要继续的写其他的计算功能,就要再修改很多的代码
极大简化了之后的代码添加量
转移表
函数指针加上函数指针数组
函数指针数组指针
回调函数
冒泡排序
//冒泡排序(只能排序整型数组)
//插旗优化,已经有序的话(第一趟一个都没有交换)直接退出循环
#include <stdio.h>
int main()
{
int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int j = 0;
int flag = 1;
//趟数
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = 0;
}
if (flag == 1)
{
break;
}
}
}
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
qsort的介绍
库函数qsort,C语言提供的快速排序的思想实现的一个排序函数
可以直接用它,排序任意类型的数据(默认升序,除非自己改一下返回值的位置)
e1和e2是指针,指向的是两个待比较的数组元素
void qsort(void* base, //待排序数据的起始地址
size_t num, //待排序数据的个数
size_t width, //待排序元素的大小
int (*cmp)(const void* e1, const void* e2))//cmp是个指针,指向的是函数,函数的两个参数是void* ,返回类型是int
//接收的是一个函数的地址,也就是要实现的函数
//e1和e2都是一个指针,指向的是两个要比较的数据
//在底层如果返回的数是大于零的话,两个数据就进行交换。因此可以通过交换e1和e2的值来选择是升序或降序
void *的是无具体类型的指针(泛型),可以接收任意类型的地址,但是不能进行解引用操作或者,进行+-整数的运算,
qsort的使用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Stu
{
char name[20];
int age;
};
int cmp_int(const void* e1, const void* e2)
{
return (*(int*)e2 - *(int*)e1);
//小细节,void*的指针是不能直接进行操作的,强转,还要解引用后才能进行比较运算
}
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
//小细节:括号要括起来,它是一个指针,先把e1想转成结构体指针后才能进行使用
}
void test1()
{
int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
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]);
}
}
void test2()
{
struct Stu s[3] = { {"zhangsan", 23}, {"lisi", 34}, {"wangwu", 25} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s, %d\n", s[i].name, s[i].age);//整体进行交换,我还以为只有名字交换,年龄没有交换
}
}
int main()
{
//test1();//排序整形数组
//test2();//排序结构体中的成员变量
return 0;
}
基于冒泡排序的思想模拟实现qsort
//实现的是两个数据的比较,直接返回相减之后的结int cmp(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2);
}
//实现的是两个数的交换
void Swap(char* buf1, char* buf2, int width)//char* 类型的,void*加加不了
{
int i = 0;
for (i = 0; i < width; i++)
{
char temp = *buf1;
*buf1 = *buf2;
*buf2 = temp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))//接收cmp这个函数!!
{
int i = 0;
int j = 0;
int flag = 1;
//趟数
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 1 - i; j++)
{
//判断两个数的大小
//把指针传过去,char*
//cmp的参数只有两个
///传过去的是两个待比较的元素的地址,j表示的是第几个下标索引的元素
if (cmp( (char*)base + j*width, (char*)base + (j+1)*width ) > 0)
{
//参数的宽度(一个元素占几个字节)参数的类型也需要,一个字节一个字节的交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
flag = 0;
}
if (flag == 1)
{
break;
}
}
}
}
#include <stdio.h>
int main()
{
int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。