指针概念介绍
指针的定义简介
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,常量指针:指针本身是常量,指针的值(即指向的地址)不能被修改。
下面分别举例说明这两种情况:
- 指向常量的指针:
#include <stdio.h>
int main() {
const int num = 10;
const int *ptr = #
// 无法通过ptr修改所指向的内容
// *ptr = 20; // 这行代码会导致编译错误
printf("num的值:%d\n", *ptr);
return 0;
}
在这个例子中,ptr 是一个指向常量 num 的指针,因此无法通过 ptr 修改 num 的值。这种情况下,指针本身是可变的,但指向的内容是不可变的。
- 常量指针:
#include <stdio.h>
int main() {
int num = 10;
int *const ptr = #
// 可以通过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);
}