C语言详解指针(指针海洋的探索,将传值与传址刻在心里)

目录

一、指针的概念

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博客icon-default.png?t=N7T8https://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,变量的值为10
    int* pa = &a;
    //输入一个整型的指针变量pa来接收存放a的地址
    printf("%d",*pa);
    //(*)表示对pa存放地址的解引用
    return 0;
}

2.2 指针变量的大小

前⾯的内容我们了解到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产生的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4 个字节才能存储。
如果指针变量是⽤来存放地址的,那么指针变的大小就得是4个字节的空间才可以。
同理64位机器,假设有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要 8个字节的空间,指针变的大小就是8个字节。
#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*指针

在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为无具体类型的指针(或者叫泛型指
针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, 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、如何规避野指针

指针初始化,如果我们明确指针的指向的话,该指哪就指向哪;当我们不知道指向哪时, 可以给指针赋值NULL. NULL 是C语⾔中定义的⼀个标识符常量,值是0;0也是地址,这个地址是⽆法使⽤的,读写该地址 会报错。
#include <stdio.h>
int main()
{
int num = 10;
int*p1 = &num
int*p2 = NULL;

return 0;
}

6.3、注意指针不要越界

⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是 越界访问。

6.4、当指针不再使用时,可以将其置为NULL,指针使用前,判断其有效性

当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的 时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问, 同时使⽤指针之前可以判断指针是否为NULL。
我们可以把指针想为野狗,不管理的野狗是非常危险的,我们可以将野狗栓在一棵树(NULL),就相对安全了;

6.2、assert函数

assert.h 头文件定义了宏 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    4
	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);
		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;
}

对于指针的学习还很多,今天先讲到这里了,点点赞吧!!!

  • 55
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值