C语言指针(Pointer)

指针与底层硬件联系紧密,使用指针可操作数据的地址,实现数据的间接访问

问题:这好好的一个变量,我定义完之后为啥不用它名字直接访问呢?我非要用间接访问,这不没事找事吗?

为什么需要指针?

  • 指针的使用使得不同区域的代码可以轻易的共享内存数据。当然你也可以通过数据的复制达到相同的效果,但是这样往往效率不太好,因为诸如结构体等大型数据,占用的字节数多,复制很消耗性能。但使用指针就可以很好的避免这个问题,因为任何类型的指针占用的字节数都是一样的(根据平台不同,有4字节或者8字节或者其他可能) 
  • 指针使得一些复杂的链接性的数据结构的构建成为可能,比如链表,链式二叉树等等
  • 有些操作必须使用指针。如操作申请的堆内存
  • C语言中的一切函数调用中,实参传递给形参的机理都是“按值传递(pass by value)”,如果我们要在函数中修改被传递过来的对象,就必须通过这个对象的指针来完成

 

定义指针 

用来保存 指针(地址) 的变量,就是指针变量。如果指针变量p1保存了变量 num的地址,则就说:p1指向了变量num 

指针即指针变量,用于存放其他数据单元(变量/数组/结构体/函数等)的首地址。若指针存放了某个数据单元的首地址,则这个指针指向了这个数据单元,若指针存放的值是0,则这个指针为空指针 

16位系统:x=2,32位系统:x=4,64位系统:x=8 

在系统中,指针占用的字节数(即指针的大小)是固定的。这是因为指针的大小主要由CPU的架构和操作系统的内存管理模式决定!因为指针的作用就是把地址表示出来

不同系统的int占的字节数确实可能会变化!在C语言中,int类型的大小可以在不同的编译器和硬件平台上有所不同

误区:

所以我们应该把*放在p前面,这样可以更好区分数据类型

int *p,p1;		//定义一个指向int*型数据的指针和一个int类型的p1

实际上int*是一个整体和其他数据类型一样!!! 

这是因为C的语法规定了在声明多个变量时,只有第一个变量前的类型修饰符(如*表示指针)会应用到所有后面声明的变量上,除非这些变量被明确地指定了不同的类型或修饰符。 

在C语言中,定义变量时,在变量名前写一个 * 星号,这个变量就变成了对应变量类型的指针变量。必要时要加( ) 来避免优先级的问题 

int (* parr )[3]; //parr是一个指向【包含3个int元素的数组】的指针变量

指针的操作

取地址:既然有了指针变量,那就得让他保存其它变量的地址,使用& 运算符取得一个变量的地址

int add(int a , int b)
{
    return a + b;
}

int main(void)
{
    int num = 97;
    float score = 10.00F;
    int arr[3] = {1,2,3};

    //-----------------------

    int* p_num = #
    float* p_score = &score;
    int (*p_arr)[3] = &arr;           
    int (*fp_add)(int ,int )  = &add;  //p_add是指向函数add的函数指针
    return 0;
}

特殊的情况,他们并不一定需要使用&取地址:

  • 数组名的值就是这个数组的第一个元素的地址。
  • 函数名的值就是这个函数的地址。
  • 字符串字面值常量作为右值时,就是这个字符串对应的字符数组的名称,也就是这个字符串在内存中的地址。

 

#include <stdio.h>  
  
int main() {  
    int arr[10] = {0};  
    printf("%p\n", (void*)arr); // 打印数组的地址,等同于&arr[0]  
    printf("%p\n", (void*)&arr[0]); // 显式地获取第一个元素的地址  
}

数组与指针 

数组名就是指针变量! 

数组是一些相同数据类型的变量组成的集合,其数组名即为指向该数据类型的指针。数组的定义等效于申请内存、定义指针和初始化 

char c[ ] = {0x33, 0x34, 0x35};

利用下标引用数组数据也等效于指针取内容: 

#include <iostream>
using namespace::std;
int main()
{
	char c[] = { 0x33, 0x34, 0x35 };
	if (c[0]==*c)
	{
		cout << "true\n";
	}
	if (c[1] == *(c+1))
	{
		cout << "true";
	}
}

注意事项 

在对指针取内容之前,一定要确保指针指在了合法的位置,否则将会导致程序出现不可预知的错误 !!!

引用空指针
尝试访问或修改空指针所指向的内存是未定义行为,通常会导致程序崩溃

int *ptr = NULL;  
*ptr = 10; // 错误:解引用空指针

未初始化的指针
未初始化的指针可能包含任何值,包括NULL或垃圾值。如果假设这样的指针不是NULL并尝试使用它,可能会导致不可预测的行为

int *ptr;  
*ptr = 20; // 错误:ptr可能不是NULL,但也不是有效的内存地址

忘记检查返回值
某些函数(如动态内存分配函数malloccallocrealloc)在失败时会返回NULL。如果忘记检查这些函数的返回值,可能会错误地解引用一个空指针

int *ptr = malloc(10 * sizeof(int));  
if (!ptr) {  
    // 处理错误  
}  
// 如果省略了上面的if语句,直接使用ptr可能会导致问题

错误的指针算术
对空指针进行算术运算(如递增或递减)在C语言中是未定义的。虽然大多数现代编译器会将这些操作视为无操作(即空指针保持不变),但依赖这种未定义行为是不安全的

int *ptr = NULL;  
ptr++; // 错误:对空指针进行算术运算

错误的比较
虽然将指针与NULL进行比较是常见的做法,但错误地将指针与整数0或错误的指针值进行比较可能会导致问题

int *ptr = NULL;  
if (ptr == 0) { // 正确,但不如使用NULL直观  
    // ...  
}  
if (ptr == (void*)0) { // 冗余且可能令人困惑  
    // ...  
}

野指针
野指针是指向已经被释放的内存的指针。虽然野指针本身不一定是NULL,但使用野指针(尤其是假设它指向有效内存)的行为与解引用空指针一样危险。

多重释放
尝试释放一个已经被释放的指针(即使它之后被设置为NULL)也是错误的。这可能导致堆损坏或程序崩溃。

指针的应

指针的应用

传递参数

使用指针传递大容量的参数,主函数和子函数使用的是同一套数据,避免了参数传递过程中的数据复制,提高了运行效率,减少了内存占用。

使用指针传递输出参数,利用主函数和子函数使用同一套数据的特性,实现数据的返回,可实现多返回值函数的设计

直接访问物理地址下的数据

访问硬件指定内存下的数据,如设备ID号等 

将复杂格式的数据转换为字节,方便通信与存储

传递返回值 

将模块内的公有部分返回,让主函数持有模块的“句柄”,便于程序对指定对象的操作 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值