C语言-指针(一)

指针是什么?

在C语言中,指针是一个变量,其值为另一个变量的地址,即直接指向内存中的某个位置。指针在C语言中非常重要,因为它们提供了直接访问内存和操作内存地址的能力。

简单来说指针里面存放着一把钥匙(即对应目标的地址,本质是变量),通过这个钥匙可以找到对应的家(即内存中的另一个变量)。

指针变量与地址

在c语言中创建变量其实就是向内存申请空间

#include<stdio.h>
int main ()
{
  int a = 0;
  printf("%p\n",&a);
  return 0;
}

这个便是a变量的地址。(&a,这里&为取地址操作符)。

既然如此我们已经拿到了a的地址(即对应家的钥匙)那我们便可以把a的地址存在另一个变量(即一个人的手上)里面。

#include<stdio.h>
int main()
{
	int a = 0;
	int* p = &a;//取出a的地址并且存储在p中。
	return 0;
}

这时候我们便创建了p这个指针变量,类型为int *类型。

指针的大小

既然指针是一个变量,那肯定也有大小,这里简单说说。

32位机器上,一个地址需要32个bit位,即4个字节存储。

64位机器上,一个地址需要64个bit位,即8个字节存储.

(注意:指针变量的大小和类型是无关的,只要是指针类型的变量,在相同平台下,大小都是相同的)

问:那各种类型的指针有什么用呢?---请看指针变量类型的意义部分

如何使用指针?

现在我们已经有了p这个指针变量,要如何使用呢?

这里需要学习一个操作符叫做解引用操作符(*)。

* 与&,在某种意义上可以说是产生相反作用的。一个为解引用,另一个为取地址。

*p = 10;

这样我们就通过p这个指针(钥匙)把a里面的值改成了10(意思好比如说p这个小偷通过a家的钥匙进入a家把a里面的东西篡改了,而不是通过a本身他自己家的东西进行改变。)

指针变量类型的意义

int main()
{
	int n = 0x11223344;
	int n1 = 0x11223344;
	int* p = &n;
	char* p1 = &n1;
	*p = 0;
	*p1 = 0;
	return 0;
}

(这里整数类型n和n1用16进制表示是为了方便观察)

通过上面代码和调试结果可以看到指针的类型决定了,程序在对指针进行解引用时的权限大小(即一次可以操作几个字节。)

如:char*的指针解引用只能访问一个字节,而int*的指针解引用就可以访问四个字节

指针运算

指针加减整数运算

int main()
{
	int n = 0x11223344;
	int* p = &n;
	char* p1 =(char*)&n;
	printf("%p\n", &n);
	printf("%p\n", p);
	printf("%p\n", p+1);
	printf("%p\n", p1);
	printf("%p\n", p1+1);
	return 0;
}

从代码结果我们可以知道:

char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。

这就是指针类型差异导致了不同指针向前或者向后走的字节数不同,即访问的内存不同

因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素

#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;
}

指针减指针的运算

指针减指针得到的是两个指针之间的元素个数(前提两个指针要指向同一块空间

int array[] = {1, 2, 3, 4, 5};
int* p1 = &array[1]; // 指向数组的第二个元素
int* p2 = &array[4]; // 指向数组的第五个元素

int diff = p2 - p1; // 结果是 3,因为p2在p1之后3个元素的位置

输出为3;

但是如果改为return p1-p2;

则输出为-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;
}

输出结果为1 2 3 4 5 6 7 8 9 10。

(ps:arr(数组名)就是数组首元素的地址,所以下面两种形式等价)

int*p=&arr[0];
int*p=arr;
while(p<arr+sz)

这行代码中arr为数组首元素的地址,表示当p的地址小于arr+szarr+sz 实际上是数组 arr 最后一个元素后面一个位置的指针)时循环打印p指针所指向的内容(即把数组的所以内容都打印然后才会跳出循环。

利用const防止修改指定内容

int main()
{
	const int n = 1;
	n = 10;
	return 0;
}

这里面由于n被const修饰了所以n无法被修改,代码无法被编译过去。但是

int*p=&n;
*p=10;

我们可以通过p去修改n,但是这个是在打破语法规则,因为n加上const就是不想被修改。

const int*p=&n;

如果代码改成这样子那p也无法改变n了。

那const放置的位置有无影响呢?答案是有。

const的放置位置影响

• const如果放在*的左边,修饰的是指针指向的内容(即不能改变),但是指针变量本⾝的内容可变。(通俗来讲就是只能改变钥匙对应的家里面的东西)
• const如果放在*的右边,修饰的是指针变量本⾝(即不能改变),但是指向的内容可以通过指针改变。(通俗来讲就是只能改变钥匙指向的家的地址,从偷一个家变成偷另一个家)

什么是野指针?

野指针,并不是一个正式的编程术语,但它在程序员之间的口头及网络上广为流传。野指针指的是那些没有被初始化或者已经释放了的内存地址的指针。这种指针的危险之处在于,它指向的内存区域是不可预测的,甚至可能是非法的。

野指针的成因

野指针的产生通常有以下几种情况:

  1. 未初始化的指针: 分配了指针变量但没有明确初始化,它就可能指向任何地方。(会导致访问冲突,系统崩溃)

    int *p; // 未初始化的指针
    *p=10;
    
  2. 已释放的内存: 当我们使用freedelete释放了一块内存后,如果没有将指向该内存的指针设为NULL,该指针仍然指向原来的地址。或者在函数结束时把变量的地址带出去了,但是变量的地址已经销毁了。

    int *p = malloc(sizeof(int));
    *p = 10;
    free(p); // 释放内存后,p成为野指针
    //或者
    int* test()
    {
     int n = 100;
     return &n;
    }//函数结束之后n自动销毁,但是因为return &n 导致n的地址被赋给了p
    int main()
    {
     int*p = test();
     printf("%d\n", *p);
     return 0;
    }
  3. 越界的指针操作: 指针操作超出了变量的作用范围,这也可能产生野指针。

    int arr[10];
    int *p = &arr[10]; // 越界
    

    arr数组的下标就到9。

野指针的危害

野指针可能导致各种难以预料的问题,包括但不限于:

  • 程序崩溃
  • 数据损坏
  • 难以追踪的bug
  • 安全漏洞

如何避免野指针?

  1. 初始化所有指针: 声明指针时,尽量初始化为NULL或有效的内存地址。

    int *p = NULL; // 初始化为NULL
    
  2. 释放内存后置空指针: 释放内存后,立即将指针置为NULL

    free(p);
    p = NULL; // 置空指针
    
  3. 小心指针运算: 指针运算时要确保不会越界,也就是确保指针只能访问申请的空间,不能超出范围访问,并时刻警惕指针的合法性。

  4. 检查有效性:

    p = &arr[0];//重新让p获得地址
     if(p != NULL) //判断
     {
     //...
     }
    assert(p!=NULL);

    利用assert断言也是可以在一定程度上避免野指针的。(assert需要包含头文件#include<assert.h>

上⾯代码在程序运⾏到这⼀⾏语句时,验证变量 p 是否等于 NULL(c语言中NULL可以近似看成0) 。如果确实不等于 NULL ,程序继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。

如果你想要更多关于C语言编程的内容,请关注我的CSDN博客。下次我将探讨如何区分函数指针,指针数组,数组指针和其他进阶操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值