C语言之指针详解(1)

什么是指针:

指针(pointer)是一种数据类型(整数),这种类型定义出的变量叫指针变量(简称指针)。
计算机的内存是由一个个字节组成的。内存的每个字节都有一个整数编号(十六进制),这个编号通常叫做地址。指针中存储的就是地址。
1bit 是最小的可用单位,能存储1或0;1byte = 8bit;1kb = 1024byte;1mb = 1024kb;1gb = 1024mb;1tb = 1024gb;1pb = 1024tb;

为什么使用指针:

  1. 堆内存不能与标识符(变量名)建立联系,必须与指针配合才能使用堆内存。
  2. 优化参数的传递效率,函数的传参是值传递(内存拷贝),如果只传递变量的地址(4字节),也可用达到传参的目的。
  3. C语言中函数是传值调用。而这个值的类型可以是非指针类型或指针类型。如果函数调用时,从函数外部传给函数的值是非指针类型(如整数,小数,字符等),那么被调函数的任何操作,都不能改变主函数的变量值。若想要在函数体中改变主函数的变量值,那么传的应该是指针类型(即传变量的地址),这样在被调用的函数中,可以利用指针间接访问来改变主函数的变量值。

注意:指针有一定的危险性,不该用的时候不要乱用。

如何使用指针:

定义指针变量:
类型* 变量名;
  1. 指针变量与普通变量一样默认值不确定,一般可以初始化为NULL。
  2. 指针变量的用法与普通变量不同,通过名字把指针变量与普通变量区分。
  3. 指针变量中只存储了一个字节的内存地址,当通过指针变量访问内存时由指针类型决定
  4. 不能连续定义指针变量。
		int* p1,p2;		//p1是指针,p2是int类型变量
		int *p1,p2;		//一个\*只能定义一个指针变量
为指针变量赋值:
		int num;
		int* p = #		//把栈内存赋值给指针变量
		p = malloc(4);		//把堆内存赋值给指针变量

注意:如果指针变量存储的地址是非法的,则访问内存时就会出现段错误。

通过指针变量访问内存(解引用):

“ * ”间接访问运算符,是一个单目运算符,间接访问指针所指向的对象。“ * ”运算符的操作对象必须是指针(地址)。
注意:“ * ”有两种含义。定义指针变量时表示的是该变量是指针变量,其他情况表示对指针进行解引用,是一个间接访问符。

int num = 10;
int* p = #
// *p <=> num 是等价的

注意:%p,可以显示一个指针存储的地址值

使用指针要注意的问题:

空指针:指针变量的值为NULL,我们把这种指针称为空指针。空指针也是一种错误标志,当一个函数的返回值为NULL时表示函数执行出错。

#include <stdio.h>
#include <stdlib.h>
//函数出错,返回NULL
int main()
{
	int* p = malloc(~0);		//取到了一个非常大的地址
	printf("%p\n",p);
}
/*
out:
(nil)
*/

注意:在大多属系统下NULL就是0地址,而0地址存储的是系统复位时的一些数据,因此对空指针解引用会引发段错误。空指针肯定是NULL,NULL不一定是0。
如何杜绝空指针导致的段错误:对来历不明的指针(如函数参数)进行解引用时要先判断是否为NULL。

野指针:

野指针变量中存储的值是不确定的。
使用野指针的后果:一切正常,段错误,脏数据。
注意:使用野指针不一定会出错,但是野指针的危害比空指针更严重,因为野指针无法通过条件判断出(只能对代码进行分析)。

如何避免野指针所造成的错误:
所有的野指针都是人为造出来的,不制造野指针也就不会有野指针。

  1. 定义指针时一定要初始化,如果不知道该赋什么值就赋值NULL。
  2. 函数不应该返回局部、块变量的地址。
  3. 释放指针指向的内存空间时,立即将指针重置为NULL。
  4. 当一块堆内存被释放后,指向它的指针应该立即置NULL。
    #include <stdio.h>
    int* func(void)
    {
    	int num = 100;
    	int* p = &num;
    	return p; 			//不应该返回局部变量的地址
    }
    void test(void)
    {
    	int arr[10] = {1,2,3,4,5,6,7,8,9};
    }
    
    int main()
    {
    	int* p = func();
    	printf("%d\n",*p);
    	test();
    	printf("%d\n",*p);
    }
    /*
    out:
    10
    (不确定数)
    */
    

指针的运算:

注意:指针变量中存储的就是代表内存地址的整数。整数能使用的运算符,指针变量理论上来说应该都可以使用,但不是所有的运算都有意义。

对于指针类型数据,除了间接引用运算、赋值运算等操作外,还可以有指针加减整数、指针相减及指针间比较运算,除此以外的运算都是非法的。

  1. 指针 + 整数 = 指针 + 类型字节数 * 整数
    指针 - 整数 = 指针 - 类型字节数 * 整数
    (指针加减一个整数相当于前后移动)
  2. 指针 - 指针 = (指针 - 指针)/ 类型字节数
    (指针减指针可以计算出两个指针之间相隔多少个元素)
  3. 指针 ==、!=、>、<、>=、<= 指针
    (判断指针的前后位置关系)

指针与数组:

在C语言中,如果定义了一个数组,则在编译时系统会为数组分配足够大的内存单元,按元素的下标顺序依次存储所有元素。数组名就是这个内存单元的起始地址即指针,它指向数组的第一个元素。

数组名就是一种特殊的常指针(不能赋值、修改),它与数组元素的首地址是对应关系(指针是指向关系)。因为数组名就是指针,所以可以使用指针的语法,而指针也可以使用数组的语法。

int arr[] = {1,2,3,4,5};
for(int i = 0;i<5;i++)
	printf("%d %d\n", *(arr+i),arr[i]);
//	*(arr+i) <=> arr[i]; 	两者是等价的

arr 与 &arr 的区别:

#include <stdio.h>
int main()
{
	int arr[5] = {1,2,3,4,5};
	int* p = &arr[0];
//	可以看到p存储的地址,arr的地址和arr存储的地址都是相同的。
	printf("%p %p %p %p\n",&p,p,&arr,arr);
	
//	不同的是,&arr指向的是一个数组,arr指向的是一个整型。
//	当&arr+1时,它将跳过数组中所有元素,指向下一个数组。
//	当arr+1时,它将指向下一个数组元素。
	int* p1 = (int*)(&arr+1); 
	printf("%p %p\n",p1 ,arr);
}
/*
out:
0xbff37f68 0xbff37f54 0xbff37f54 0xbff37f54
0xbff37f68 0xbff37f54	(相差了20个字节:arr定义了数组中有5个int整型)
*/

arr类型:int*(即指针)
&arr类型:int (*arr)[5](即数组指针)
数组指针:指向数组的指针。如:int (*arr)[5];
指针数组:由指针变量组成的数组。 如:int* arr[5];

指针与const:

  1. const int* p :保护的是指针变量所执行的内存,不能通过解引用来修改内存中的数据。解决提高传递效率带来的安全隐患。

  2. int const * p :同上。

  3. int* const p :保护指针变量的值不会被显式的修改。指针将不会指向别的地址,防止变成野指针。

  4. const int* const *p:既保护指针变量又保护指针变量所指向的内存。

  5. int const * const p:同上。

    #include <stdio.h>
    // 解决提高传递效率带的安全隐患
    void func(const double* p)
    {
    	*p = 3.14; 						//error
    }
    
    int main()
    {
    	const int num = 10;
    	const int* p = NULL;
    	*p = num;						//error
    	
    	int* const p1 = NULL;
    	p1 = &num;						//error
    	double f = 0;
    	func(&f);
    }
    
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值