指针(c语言)

一.指针的定义

1.内存被划分为一个一个内存单元,对内存单元进行编号,这些编号称为内存单元的地址,

其中指针就是存放地址的容器

2.平常说的 指针,其实就是指针变量

注意:

1个内存单元的大小是1个字节**,如果是一个** int类型****的变量,就会有 4个内存单元


二.指针变量

我们可以通过&(取地址)操作符来取出变量的内存其实就是地址,把地址放在一个变量中,这个变量就是指针变量

cs 复制代码
#include <stdio.h>
 int main()
 {
 int a = 10;//在内存中开辟一块空间
int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,因为它是int类型,这里是将a的4个字节的第一个字节的地址存放在p变量
中,p就是一个之指针变量。
return 0;
 }

1.指针变量的大小

1.在32位机器上,地址是由二进制(0或1)组成的,1个二进制就是1个bit(比特),32位二进制,那也就是32个bit(比特),比特转换为字节是8进1,所以就是4个字节

2.在64位机器上,也就是有64个二进制,也就是64bit(比特位) ,也就是8个字节

总之: 在32位机器上,指针变量的大小是4个字节

在64位机器上,指针变量的大小是8个字节


三.指针和指针的类型

指针有整数类型,浮点数类型,字符类型

那他们的大小怎么样,如下

总之:

在64位(x64)操作系统,指针类型大小统一为8个字节

在32位(x86)操作系统下,指针类型大小统一为4个字节

1.指针类型的意义

指针类型决定了+1或者-1时往后访问几个字节

若int、float、double。short类型变量转成char类型指针,+1往后访问1个字节,-1往前访问1个字节

若char类型变量转成int、float、double。short类型指针, +1往后访问4个字节,-1往前访问4个字节

2.指针+-整数

cs 复制代码
int main()
{
	int n = 10;
	char* pc = (char*)&n;
	int* pi = &n;
    //pc是指针变量存放n的指针,所以他们俩的地址相同
	printf("%p\n", &n);
	printf("%p\n", pc);
	//把整数类型转换为字符类型,由4个字节变成1个字节
	//所以pc+1是往后访问1个字节
	printf("%p\n", pc + 1);
	printf("%p\n", pi);
	//这是整数类型的指针,+1往后访问4个字节
	printf("%p\n", pi + 1);
	return  0;
}

运行结果

这里显示的是十六进制👆

那么为什么000000DA66EF9F4到000000DA66EF9F5增加了1个字节呢?

因为内存被划分为了一个个内存单元,每个内存单元的大小是1个字节,内存单元里头放的是地址

那么000000DA66EF9F4到000000DA66EF9F5,也就是走到了下一个内存单元,所以就增加了1个字节。

3.指针-指针

C 语言规定

这个减法操作的结果是两个指针所指向的地址之间相隔的元素个数。

cs 复制代码
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[9]);
	int n = &arr[9] - &arr[0];
	printf("%d\n", n);
	return 0;
}

程序运行👇

例:实现strlen函数的功能

cs 复制代码
char my_strlen(int* pa)//用指针来接受数组名的首元素的地址
{
	char* start = pa;
	while (*start !='\0')//字符串是以'\0'结尾,排除'\0'的情况.
	{
		//刚好访问到了\0的地址,但因*start !='\0'条件跳出循环
		start++;
	}
	return start - pa;
}
                                    

int main()
{
	char arr[] = "abcdef";
	/*int ret = my_strlen(arr);*/
	printf("%d", my_strlen(arr));//数组名代表了首元素的地址,所以不用取地址

	return 0;
}

代码运行

4.指针的关系运算

cs 复制代码
//宏定义
#define N_VALUES 5
//创建数组
int values[N_VALUES];
//创建指针变量
int* vp;

int main()
{
	for (vp = &values[N_VALUES]; vp > &values[0];)
	{
		//先对其解引用,后再-1.
		*--vp = 0;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", values[i]);

	}
	return 0;
}

代码运行


四.指针和数组

1.数组名代表首元素的地址

注意:

1.sizeof(arr):这里头数组名表示的是整个数组,计算的是整个数组的大小。

2.对数组名取地址,表示的是整个数组的地址。

1.应用指针对数组进行遍历

数组元素是连续存放的,并且由低地址想高地址进行存放的,那么就可以对指针变量+1或者-1,找到数组中元素所对应地址,对其解引用,进而访问数组中的元素。

代码如下:

实现数组的遍历

cs 复制代码
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* pa = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		//先对指针变量解引用,进而访问数组中的元素,后再++
		printf("%d ", *(pa++));
	}
	return 0;
}

五.野指针

概念:野指针就是指针指向的位置是不可知(随机的,正确的,没有明确限制)

1.野指针成因

指针未初始化

cs 复制代码
#include <stdio.h>
 int main()
 {   
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
 return 0;
 }

指针越界访问

cs 复制代码
include <stdio.h>
 int main()
 {
 int arr[10] = {0};
 int *p = arr;
 int i = 0;
 for(i=0; i<=11; i++)
    {
 //当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
    }
 return 0;
 }

2.如何避免出现野指针

1.指针初始化

2.小心指针越界

3.指针指向空间释放,及时置NULL

4.避免使用局部变量的地址

5.指针使用之前检查其有效性


六.二级指针

一级指针存放的是变量的地址,那么二级指针存放的是一级指针的地址

cs 复制代码
#include<stdio.h>
int main()
{
    int a =10;
//一级指针存放的是a的地址
    int* pa =&a;
//二级指针存放的是pa这个指针变量的地址
    int** ppa=&pa;
    return 0;
}

图解如下👇

七.字符指针

字符指针存放字符的指针,准确的来说,是把字符首元素的地址存放在指针变量

cs 复制代码
int main()
{
	//字符串的首元素的地址被存放再pstr的指针变量了
	const char* pstr = "Hello";
	//直接打印字符串
	printf("%s\n", pstr);
	return 0;
}

代码运行

例子

cs 复制代码
#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;
}

运行结果

str1[]和str2[]是俩个数组,他们俩个数组的地址不相同,所以ptr1 !=ptr2

str3和str4存放的是同一个字符串的地址,所以str3和str4地址一样,即str3 ==str4


八.指针数组

指针数组就是存放指针的数组

int* arr[]={&a,&b,&c};

arr是一个数组,数组中由三个元素,且是元素的地址

cs 复制代码
int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	//指针数组
	//存放地址的数组
	int* arr[] = { &a,&b,&c };
	int i = 0;

	while (i < 3)
	{
		//对其解引用,进而通过地址访问数组中的元素
		printf("%d ", (*arr[i]));
		i++;
	}
	return 0;
}

1.二级指针的指针数组

int **arr3[5];


九.数组指针

数组指针指的是存放数组地址的指针

int (*p)[10];

解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针。

[ ]的优先级是高于 *,所以需要对其加()

利用数组指针的知识实现对一维遍历

cs 复制代码
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int(*p)[10] = &arr;
	int i = 0;
	//遍历,打印元素
	for (i = 0; i < sz; i++)
	{
        //这俩个printf等价
		//*p代表数组指针
		printf("%d ", *((*p) + i));
        //printf("%d",(*p)[i])
	}
	return 0;
}

代码运行

利用数组指针对二维数组实现遍历

二维数组的起始是一维数组的数组

二维数组中每一行都当成一个首元素

所以二维数组的第一行是首元素的地址

十.一维数组的传参

以下5种方式均可用于一维数组的传参

cs 复制代码
#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};

十一.二维数组的传参

cs 复制代码
//二维数组的传参
void test(int arr[3][5])
{}
//二维数组行可以省略,列不可以省略
void test(int arr[][5])
{}
//利用数组指针,存放二维数组的首地址
void test(int(*arr)[5])
{}




int main()
{
	int arr[3][5] = { 0 };
	return 0;
}

十二.函数指针

函数指针就是存放函数的指针

这俩个代码即使没有对test取地址,得到的函数的地址都是一样,这说明了函数名已经表示了函数的地址,所以对不对其取地址都一样。类似于数组

int(*pf)(int, int) = Add;

首先pf首选与*结合说明他是一个指针变量,然后指向一个函数,指向的函数有俩个参数,返回类型是int,说明它是一个函数指针

函数调用

cs 复制代码
void Add(int x, int y)
{
	int result = x + y;
	printf("%d ", result);
}

int main()
{
	//存放函数的指针,就是指针变量
	int(*pf)(int, int) = Add;
	//这三种调用方式都可以

	//通过解引用函数指针进行调用
	(*pf)(2,3);
	//函数指针可以直接调用,编译器会自动处理
	pf(1, 1);

	Add(0, 0);
	return 0;
}

代码运行


十三.函数指针数组

函数指针数组就是把函数的地址存放在一个数组中

int ( *parr1[10] ) ( );

parr1先与[ ]结合说明他是一个数组,存放的是int (*)()类型的函数指针

例题

十四:指向函数指针数组的指针

cs 复制代码
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}


int main()
{
	//函数指针
	int(*pf)(int, int) = Add;
	//函数指针数组
	int(*pfArr[4])(int, int) = { Add,Sub };
	
	int(*(*pffArr)[4])(int, int) = &pfArr; 
	//pffArr是一个指向函数指针数组的指针变量
	//首先pffArr与*结合说明他是一个指针,该指针指向一个方括号说明他是指向的数组
	//并且数组中的每一个元素是函数指针
	return 0;
}

十五.回调函数

回调函数的定义:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进 行响应。

例题:计算器的+ - * /的实现

cs 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
//制作菜单
void menu()
{
	printf("***********************\n");
	printf("***********************\n");
	printf("*******1.add  2.sub********\n");
	printf("*******3.mul  4.div****\n");
	printf("*********0.exit***********\n");
	printf("***********************\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(*pa)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入俩个操作数");
	scanf("%d %d", &x, &y);
	//通过函数指针来调用函数,比如说Add函数,pa就是等价于Add函数
	//不需要对其解引用,编译器会自动处理
	ret = pa(x, y);
	printf("%d\n", ret);
}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择\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;
		default:
			break;
		}
	} while (input);
	
	return 0;
}

结语:作为初学者,可能对指针理解不太全面,本篇文章不足之处在所难免,如有错误还望大家纠正下,望大家多多包涵,谢谢大家!

相关推荐
雾削木8 分钟前
C# WPF Material DesignThemes 5.0 命名规则改变后导致找不到资源
开发语言·c#·wpf
DevOpsDojo10 分钟前
Bash语言的并发编程
开发语言·后端·golang
矮油0_o18 分钟前
Ⅱ.INTRODUCTION TO CUDA C (CUDA C 入门)
c语言·开发语言·gpu算力·cuda c
天弈初心19 分钟前
python:利用神经网络技术确定大量离散点中纵坐标可信度的最高集中区间
开发语言·python·神经网络
醒了就刷牙31 分钟前
黑马Java面试教程_P11_技术场景
java·开发语言·面试
weixin_4825655343 分钟前
液显ID读卡器C#小程序开发
开发语言·c#
初级代码游戏1 小时前
C# 修改项目类型 应用程序程序改类库
开发语言·c#·项目类型
CyberScriptor1 小时前
Elixir语言的正则表达式
开发语言·后端·golang
BinaryBardC1 小时前
F#语言的数据结构
开发语言·后端·golang
SyntaxSage2 小时前
Swift语言的软件工程
开发语言·后端·golang