目录
一、指针的概念
1.1内存与地址
例子:
二、变量的指针与指针变量
2.1、指针变量的定义及使用
1、指针变量的定义
2、指针变量的使用
2.2 指针变量的大小
2.3、指针+-整数
2.4、void*指针
三、指针的运算
1、指针+- 整数
2、指针-指针
3、指针的关系运算
6. 野指针
6.1、野指针成因
6.2、如何规避野指针
6.3、注意指针不要越界
6.4、当指针不再使用时,可以将其置为NULL,指针使用前,判断其有效性
6.2、assert函数
四、多级指针及指针数组
(1)多级指针
五、计算器(转移表)的使用
1、计算器的实现(switch)
什么是转移表?
对于指针的学习还很多,今天先讲到这里了,点点赞吧!!!
一、指针的概念
要知道指针的概念,要先了解变量在内存中如何存储的。在存储时,内存被分为一块一块的。每一块都有一个特有的编号。而这个编号可以暂时理解为指针,就像酒店的门牌号一样。
1.1内存与地址
在讲内存和地址之前,我们想有个⽣活中的案例:
假设你要去酒店,酒店有100个房间,但是房间没有编号,你的⼀个朋友来找你玩,如果他想要找到你,就得一个一个去找,这样的效率是很低的,那我们给定每个房间编号
⼀楼:101,102,103...
⼆楼:201,202,203....
...
你的朋友得到房间号,就可以快速找到你的房间,找到你。
如果把上面的例子对找到计算机中:
如: 你的朋友 就相当于计算器
而你就是房间(地址)的内存了
101 102 103 就相当于地址
在计算机中我们把内存单元的编号也称为地址。C语言中给地址起了新的名字叫:指针
例子:
void main(){int x = 1, int y = 2;}
这段代码非常简单,就是两个变量的声明,分别赋值了 1、2。我们把内存当做一个酒店,而每个房间就是一块内存。那么“int x = 1;”和“int y = 2;”的实际含义如下:
去酒店订了两个房间,门牌号暂时用 px、py 表示
让 1 住进 px,让 2 住进 py
其中门牌号就是 px、py 就是变量的地址
x 和 y 在这里可以理解为具体的房间,房间 x 的门牌号(地址)是 px,房间 y 的门牌号(地址)是 py。而 1和 2,通过 px、py 两个门牌,找到房间,住进 x、y。
如果你想要更直观的观察,可以观看上节中VS调试的监视
VS实⽤调试技巧-CSDN博客https://blog.csdn.net/Asuku_/article/details/137396302?spm=1001.2014.3001.5502
二、变量的指针与指针变量
变量的指针就是变量的存储地址,指针变量就是存储指针的变量。
2.1、指针变量的定义及使用
1、指针变量的定义
指针变量的定义形式如:数据类型 *指针名;例如:
//分别定义了 int、float、char 类型的指针变量
int *x;
float *f;
char *ch;
这里的指针变量是x,f,ch,并非 *x , *f, *ch.
数据名为int* , float*, char*
2、指针变量的使用
我们要怎样取地址呢?这就要用到——取地址运算符& 和 指针运算符*(间接寻址符)
取地址运算符&:单目运算符&是用来取操作对象的地址。例:&i 为取变量 i 的地址。对于常量表达式、寄存器变量不能取地址(因为它们存储在存储器中,没有地址)。
指针运算符*(间接寻址符):与&为逆运算,作用是通过操作对象的地址,获取存储的内容。例:x = &i,x 为 i 的地址,*x 则为通过 i 的地址,获取 i 的内容。
int main() {int a = 10;//输入一个整型变量a,变量的值为10int* pa = &a;//输入一个整型的指针变量pa来接收存放a的地址printf("%d",*pa);//(*)表示对pa存放地址的解引用return 0;
}
2.2 指针变量的大小
#include <stdio.h>
//指针变量的⼤⼩取决于地址的大小
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
printf("%zd\n", sizeof(char *));
printf("%zd\n", sizeof(short *));
printf("%zd\n", sizeof(int *));
printf("%zd\n", sizeof(double *));
return 0;
}
怎么回事呢?想必你也发现些许奥秘,没错,对于指针来说,指针变量的大小与类型无关、只要指针类型的变量,在相同的平台下,大小都是相同的。
2.3、指针+-整数
#include <stdio.h>
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;printf("%p\n", &n);printf("%p\n", pc);printf("%p\n", pc+1);printf("%p\n", pi);printf("%p\n", pi+1);return 0;
}
让我们打印一下吧!!!
我们可以看得出来 ,char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。 这就是指针变量的类型差异带来的变化。
2.4、void*指针
#include <stdio.h>
int main()
{int a = 10;int* pa = &a;char* pc = &a;return 0;
}
显示从int*到char*的类型不兼容,编译器会给一个报错,用void*则不会出现这个问题
#include <stdio.h>
int main()
{int a = 10;void* pa = &a;void* pc = &a;return 0;
}
这里我们看到,void*可以用来接收不同类型的指针,但是注意的是void*不能用来指针的运算。
三、指针的运算
1、指针+- 整数
#include <stdio.h>
//指针+- 整数
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i)); //p+i 这⾥就是指针+整数
}
return 0;
}
2、指针-指针
//指针-指针
#include <stdio.h>
int my_strlen(char *s)
{
char *p = s;
//每次++,到达下一个元素,到最后一个为NULL时,p指向c
while(*p != '\0' )
p++;
return p-s;
//返回地址的差值,因为是char*类型,每个地址间跳过一个字节,所以返回的}
int main( )
{
printf("%d\n", my_strlen("abc"));
return 0;
}
3、指针的关系运算
//指针的关系运算
#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);while (p < arr + sz) //指针的大小比较{printf("%d ", *p);p++;}return 0;
}
6. 野指针
6.1、野指针成因
指针未初始化:
#include <stdio.h>
int main()
{int *p;//局部变量指针未初始化,默认为随机值
*p = 20;return 0;}
指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = &arr[0];
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
指针指向的空间释放
#include <stdio.h>
int* test()
{
int n = 100;
return &n;
}int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
6.2、如何规避野指针
#include <stdio.h>
int main()
{
int num = 10;
int*p1 = &num
int*p2 = NULL;return 0;
}
6.3、注意指针不要越界
6.4、当指针不再使用时,可以将其置为NULL,指针使用前,判断其有效性
6.2、assert函数
assert(p != NULL );
运行这个语句的时候,判断p是否为空,如果不为空则,程序继续运行,如果为空,则终止程序,并给出错误的信息提示。
四、多级指针及指针数组
(1)多级指针
指针变量作为一个变量也有自己的存储地址,而指向指针变量的存储地址就被称为指针的指针,即二级指针。依次叠加,就形成了多级指针。
int p = 10;
//设置一个变量为p
int* pc = &p;
//取p的地址存进pc
int** pt = &pc;
//取pc的地址存进pt,这里的pt为二级指针
先用一个简单的数组来举例:
int nums[2][2] = { {1, 2},{2, 3}};
#include<stdio.h>int main()
{int arr[2][2] = { {1,2},{2,3} };int* pc = &arr;printf("%d ", *pc);printf("%d\n", pc);printf("%d ", *(pc+1));printf("%d\n", (pc + 1));printf("%d ", *(pc+2));printf("%d\n", (pc + 2));printf("%d ", *(pc+3));printf("%d\n", (pc + 3));return 0;
}
我们可以看出二维数组地址的运用,可以看作一维数组;以此来推断,面对多维数组地址的运用时,我们不必害怕,可以当作一维数组
4.3、指向函数的指针
C 语言中,函数不能嵌套定义,也不能将函数作为参数传递。但是函数有个特性,即函数名为该函数的入口地址。我们可以定义一个指针指向该地址,将指针作为参数传递。
对于函数参数传递时,如果是传址函数时,则可以改变该地址的函数值;如为传值,则不然;
#include<stdio.h>void comp_int(int*comp) {*comp = 10;
}int main()
{int a = 10;int b = 20;comp_int(&b);if (a == b) {printf("相等");}return 0;
}
这里的输出为:相等,则我们可以通过传址改该地址的值;
五、计算器(转移表)的使用
1、计算器的实现(switch)
对于一个计算器,需要有最基础的(加减乘除),来人,上代码:
这时候有人就会觉得这个代码有点冗长,说:小伞,小伞有没有办法,能将代码变得便洁吗?
那当然是有的呀!
#define _CRT_SECURE_NO_WARNINGS#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;
}int main() {int a = 0;int x, y;int ret;do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("请选择:");scanf("%d", &a);switch (a){case 1:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;}} while (a);return 0;
}
如果我们使用switch语句来实现这样一个简易的计算器我们会发现,每当我要添加一个功能的时候。都需要增加一个case语句,比如我要增加一个&运算,我得再加上一个case语句。因此我们可以使用函数指针数组(转移表)来实现,会简易很多。
什么是转移表?
其实很简单,它所指的就是运用函数指针数组以数组方式去调用里面的函数,从而在某些情况下替代冗长的switch函数,就叫转移表。
单纯的文字说明实在有些单调,这里通过模拟实现计算器来进一步解释说明转移表。
上代码:
#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;
}
int main()
{int x, y;int input = 1;int ret = 0;//p[]={add,sub,mul,div}//这里我们想要将函数的地址存进来//int(*p[])(int,int)//返回类型 函数指针 指向函数的参数int(*p[5])(int , int )= { 0, add, sub, mul, div }; //转移表//函数指针的数组 下标 0 1 2 3 4do{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 <= 4 && input >= 1)){printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = (*p[input])(x, y); //printf("ret = %d\n", ret);}else if (input == 0){printf("退出计算器\n");}else{printf("输⼊有误\n");}} while (input);return 0;
}