C语言——指针详解

指针概念介绍

指针的定义简介

1,定义:指针是一个变量,其值为另一个变量的地址。通过指针,我们可以直接访问内存中的数据。

2,本质:在计算机内部,每个变量都存储在内存中的某个位置,这个位置有一个唯一的地址。指针本质上就是存储另一个变量地址的变量。

3,指针的作用:指针可以用于动态内存分配、数组操作、函数传参等各种场景。通过指针,可以更高效地访问和处理数据。

4,指针的危险性:指针操作需要谨慎,因为错误的指针使用可能导致程序崩溃或内存泄漏。例如,未初始化的指针可能包含随机值,解引用这样的指针可能导致未定义行为。

比喻:指针就好像世界地图上的经纬度,可以帮助我们快速找到对应的地点位置

指针的定义方式

1,指针符号:指针变量通常使用 * 符号来声明,同时指针也分为多种类型的指针,和数据一一对应,比如:

int* ptr1;
char* ptr2;
float* ptr3;

2,指针的内容 :指针的内容为某一个地址,地址一般用取地址符号 & 来获取。比如:

int a = 10;
int* p = &a;

3,指针内容的获取:有了指针,用解引用符号 * 来解开地址,取得地址当中的值。比如:

int a = 10;
int* p = &a;
int c = *p;

4,注意事项:不是所有的地址都可以随意访问的,有些地址的东西是不允许访问和修改的,所以指针的使用要慎重,不然就会引发错误。

二级指针

二级指针概念

1,定义:二级指针依旧是指针,不过储存的是一级指针的地址,是一个指针的指针。

2,使用与写法:二级指针写法与一级指针类似,依旧是用去地址符取的一级指针的地址,如下:

int a = 10;
int *p = &a;
int **ptr = &p;

3,二级指针的解引用:通过二级指针可以访问和修改指针指向的一级指针的值,以及通过一级指针间接访问它所指向的变量。使用两次*运算符可以进行解引用操作。例如:

int num = 10; 
int *ptr = #
 int **ptr2 = &ptr; 
printf("%d", **ptr2);

//**ptr2将输出num的值10。

4,二级指针的应用场景:二级指针常用于函数参数传递,特别是在需要修改指针本身的值时。通过将指针的地址传递给函数,并在函数内部对指针进行修改,可以实现对指针的间接修改。

5,动态分配二级指针:可以使用动态内存分配函数(如malloc())来为二级指针分配内存空间。动态分配的二级指针可以用于创建动态的一维或二维数组。

应用例子:

#include <stdio.h>
#include <stdlib.h>

void updateValue(int **ptr) {
    int newValue = 20;
    **ptr = newValue;
}

int main() {
    int value = 10;
    int *ptr = &value;
    int **ptr_to_ptr = &ptr;

    printf("原始值: %d\n", *ptr);

    updateValue(ptr_to_ptr);

    printf("更新后的值: %d\n", *ptr);

    return 0;
}

更高级指针于此类似

空指针与野指针

空指针

1,定义:空指针是指不指向任何有效内存地址的指针。在C和C++中,空指针通常用NULL(在C中)或nullptr(在C++中)表示。空指针通常用于以下情况:

2,指针初始化:在定义指针变量时,如果没有立即给指针赋予有效的内存地址,可以将其初始化为NULL或nullptr,以表示该指针当前不指向任何有效的内存地址。

int *p = NULL

3,指针检查:在使用指针之前,通常需要检查指针是否为空,以避免对空指针进行解引用操作而导致程序崩溃或未定义行为。

void func(int *p)
{
	if(p == NULL)
	{
		return;
	}
}

4,指针赋值:可以将指针重新赋值为NULL或nullptr,表示指针不再指向之前的内存地址,或者将指针释放后将其置为空,以避免产生野指针。

int* p = (int*)malloc(sizeof(int));
free(p);
p = NULL;

5,函数返回值:有时函数需要返回一个指针,但某些情况下无法得到有效的指针值,可以返回NULL或nullptr作为空指针表示特殊情况。

int* func(int *p)
{
	if(p == NULL)
	{
		return NULL;
	}
}

6,指针比较:在进行指针比较时,空指针通常被用来判断指针是否有效或两个指针是否相等。

7,标记结束:在一些数据结构中,空指针还可以用来标记链表、树等数据结构的结束。

需要注意的是,对空指针进行解引用操作是不安全的,可能导致程序崩溃或未定义行为。因此,在使用空指针时应该进行判空操作。另外,C++11之后推荐使用nullptr代替NULL来表示空指针,因为nullptr类型更明确,并且可以避免一些潜在的问题。

野指针

1,定义:野指针是指指向未知内存地址的指针,通常是由于指针未被正确初始化、指向的内存区域已经被释放或者指向的内存超出了作用域而导致的。野指针的存在可能会导致程序崩溃、数据丢失或安全漏洞,因此应该尽量避免使用和操作野指针

以下是一些关于野指针的详细解释:

2,未初始化指针:如果一个指针变量没有被初始化,它会包含一个随机的内存地址,这就是未初始化指针。对未初始化指针进行解引用或操作会导致未定义的行为,造成程序崩溃或产生不可预测的结果。比如:

int *p;
printf("%s",*p);

3,已释放的指针:在动态内存管理中,如果使用delete释放了内存但没有将指针置为NULL,那么这个指针就成为了野指针。对已释放的内存进行访问可能导致内存泄漏或其它严重问题。

4,指针超出作用域:如果指针指向的对象在指针所在的作用域之外被销毁,那么该指针也会变成野指针。在这种情况下,访问指针指向的内存区域可能导致未定义行为。

5,悬空指针:悬空指针是指指向已经释放的内存区域的指针,通常发生在多次释放同一块内存后未及时将指针置为NULL。使用悬空指针可能导致程序崩溃或数据损坏。

常量指针

1,在C语言中,常量指针是指一个指针本身是不可修改的,即指针本身的值是不可变的,但是指针所指向的数据是可以修改的。这里有两种常量指针的情况:

2,指向常量的指针:指针指向的内容是常量,不能通过该指针修改所指向的内容。
3,常量指针:指针本身是常量,指针的值(即指向的地址)不能被修改。
下面分别举例说明这两种情况:

  1. 指向常量的指针:
#include <stdio.h>

int main() {
    const int num = 10;
    const int *ptr = &num;

    // 无法通过ptr修改所指向的内容
    // *ptr = 20; // 这行代码会导致编译错误

    printf("num的值:%d\n", *ptr);

    return 0;
}

在这个例子中,ptr 是一个指向常量 num 的指针,因此无法通过 ptr 修改 num 的值。这种情况下,指针本身是可变的,但指向的内容是不可变的。

  1. 常量指针:
#include <stdio.h>

int main() {
    int num = 10;
    int *const ptr = &num;

    // 可以通过ptr修改所指向的内容
    *ptr = 20;

    printf("num的值:%d\n", *ptr);

    // 无法修改ptr指向的地址
    // int newNum = 30;
    // ptr = &newNum; // 这行代码会导致编译错误

    return 0;
}

在这个例子中,ptr 是一个常量指针,即指针本身是常量,不能修改它所指向的地址,但可以通过 ptr 修改指向的内容。这种情况下,指针本身是不可变的,但指向的内容是可变的。

指针数组

1,指针数组的定义:
指针数组是由指针元素组成的数组,每个元素都是指向某种数据类型的指针。

int *ptrArray[5];  // 定义了一个包含 5 个指向整型数据的指针的数组
指针数组的初始化:

2,可以通过循环或者逐个赋值的方式初始化指针数组。

int a = 10, b = 20, c = 30;
int *ptrArray[3] = {&a, &b, &c};  // 初始化一个包含 3 个指向整型数据的指针的数组

3,指针数组的访问:
可以通过下标来访问指针数组的元素,获取指针指向的具体数值。

int value = *(ptrArray[0]);  // 获取第 1 个指针所指向的整型数据的值
指针数组的应用:

4,指针数组常常用于处理多个同类型对象的情况,比如字符串数组、动态内存分配等。

char *names[3] = {"Alice", "Bob", "Cindy"};  // 一个包含 3 个指向字符型数据的指针的数组,可以用于存储字符串

函数指针

1,概念:函数指针是指向函数的指针变量,它可以在程序运行时动态地指向不同的函数。函数指针在 C 语言中被广泛使用,可以实现回调函数、动态调用函数等功能。下面我将详细介绍函数指针的相关知识。

2,函数指针的定义:
函数指针是一个指向函数的指针变量。它的声明包括函数的返回类型和参数列表。

返回类型 (*指针变量名)(参数列表);

3,例如,定义一个指向无返回值且无参数的函数指针:

void (*funcPtr)();
//函数指针的赋值:
//函数指针可以通过赋值来指向具体的函数。需要注意的是,函数指针的类型必须与所指向函数的类型一致。


// 定义一个函数
void greet() {
    printf("Hello, world!\n");
}

// 定义并初始化函数指针
void (*funcPtr)() = greet;

// 调用函数指针所指向的函数
(*funcPtr)();  // 或者可以简化为 funcPtr();

4,函数指针作为回调函数:
函数指针常常用于回调函数的实现。回调函数是指作为参数传递给其他函数,并在该函数内部被调用的函数。

// 声明一个回调函数类型
typedef int (*CompareFunc)(int, int);

// 定义一个排序函数,它接受一个比较函数作为参数
void sort(int arr[], int size, CompareFunc compare) {
    // 排序逻辑...
    int result = compare(3, 5);  // 调用回调函数
    // ...
}

// 定义一个比较函数,用于从小到大排序
int ascending(int a, int b) {
    return a - b;
}

// 调用排序函数,并传入比较函数作为回调函数
int array[] = {5, 2, 8, 1, 4};
sort(array, 5, ascending);

在上述示例中,通过将比较函数作为回调函数传递给排序函数 sort,可以根据不同的比较函数实现不同的排序方式。

根据需求还可以写成函数指针数组,可以更方便的调用同一类型的函数。

函数指针数组举例

void menu()
{
	printf("--------------------------\n");
	printf("--------1   加法 ----------\n");
	printf("--------2   减法 ----------\n");
	printf("--------3   乘法 ----------\n");
	printf("--------4   除法 -----------\n");
	printf("--------------------------\n");
}

typedef int (*pf)(int, int);

void cal(pf opra)
{
	printf("请输入操作数:");
	int a = 0, b = 0;
	scanf("%d %d", &a, &b);
	int x = opra(a, b);
	printf("%d\n", x);
}

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 divide(int a, int b)
{
	if (b != 0)
	{
		return a / b;
	}
	
	return 0;
}

void rebiao()
{
	int flag = 0;
	pf op[4] = { add,sub,mul,divide };
	do
	{
		menu();
		printf("请输入你的选择:");
		scanf("%d", &flag);
		if (flag > 0 && flag <= 4)
			cal(op[flag - 1]);
		else if (flag == 0)
			printf("退出游戏\n");
		else
			printf("选择错误,重新选择\n"), flag = 1;

	} while (flag);
}
  • 28
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值