c语言深入理解指针

一.什么是指针?

1.1指针的概念

本质上指针就是地址,口语中所说的指针,其实就是指针变量指针变量是用来存放地址的一个指针。

 我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是内存中读取的,处理后的数据也会放回内存中。

但在如今电脑内存越来越大的时代中,这些内存空间如何高效的管理?

那么就要把内存划分成一个一个的内存单元,每个内存单元的大小取1字节,每个内存单元都有一个编号。

有了内存单元的编号,CPU就可以快速找到一个内存空间。

内存单元的编号 == 地址 == 指针

二. 指针变量和地址

2.1 取地址操作符

在c语言中,创建一个变量就是向内存申请空间

上述代码创建整型变量a,向内存申请了4个字节,用于存放整数10,其中每个字节都是有地址的。 

那我们如何取到a的地址呢 ?

这里就可以用到取地址符号&

#include<stdio.h>

int main() {
	int a = 10;
	printf("%p\n", &a);
	return 0;
}

 上面的代码运行以后就可以看到

打印出了0000003EC84FFA44。这个便是a地址最小地址的字节地址。

在c语言中,创建的变量比如int类型占据4个字节,那么它会开辟连续四个字节的空间,地址就是a地址最小字节的地址。

2.2 解引用操作符 * 和指针变量

2.2.1解引用操作符 


#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;//取出a地址,并存放在指针变量中
	//int说明pa指向的对象是int类型 *说明pa是指针变量
    *pa=100;
    printf("%d",*pa);
	return 0;

}

 那么这里的输出就是100,会改变原来a的值。

 

1。*的作用是引用指针指向的变量值,引用其实就是引用该变量的地址,“解”就是把该地址对应的东西解开,解出来,就像打开一个包裹一样,那就是该变量的值了,所以称为“解引用”。 也就是说,解引用是返回内存地址中对应的对象。

解引用也可以改变该变量的数值。

2.需要注意的是,在变量声明的时候,*不能当做解引用使用,只是表示你声明的变量是一个指针类型。

2.2.2 指针变量 

我们通过取地址操作符(&)拿到的地址是一个数组,比如:0000003EC84FFA44,这个数组有时候需要存储起来,方便后期使用,那我们就可以把地址值存放在指针变量中。

 

就是把一个指针变量用作与存放a的地址 。

  指针变量也是一种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址。

 2.3 指针变量的大小

在不同的环境中其指针的大小也不同:

 

 

在64位环境中是8个字节,而在32位环境中是4个字节 ,并且与类型是无关的。

三. 指针类型变量的意义 

3.1 指针的解引用

#include <stdio.h>
int main()
{
	int n = 0x11223344;
	int* p1 = &n;
	*p1 = 0;
	return 0;
}
#include <stdio.h>
int main()
{
	int n = 0x11223344;
	char* p2 = (char *) & n;
	*p2 = 0;
	return 0;
}

 这两段代码有什么区别呢? 

 通过运行就会发现第一个就会把四个字节都改成0了,但第二个只会把指向n的地址的那一个字节改成0.

结论:指针类型决定了对指针解引用的时候有多大权限(一次可以操作几个字节)。

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

 3.2指针的加减运算

#include <stdio.h>
int main()
{
	int n = 10;
	char* pc = (char*)&n;
	int* pi = &n;
	printf("&n   = %p\n", &n);
	printf("pc   = %p\n", pc);
	printf("pc+1 = %p\n", pc+1);
	printf("pi   = %p\n", pi);
	printf("pi+1 = %p\n", pi+1);
}

运行结果:

 看的出来,指针的类型决定了向前或向后访问距离有多大。

 3.3 void指针

void* 类型,无具体类型的指针(泛型指针),void* 类型的指针大部分使用在函数参数的部分,用来接收不同类型数据的地址。

但是void* 类型的指针不能直接进行指针的+,-整数和解引用运算。

四.  const修饰指针

  1.在C语言中,const是constant的缩写,翻译是“恒定不变的”。它是定义只读变量的关键字。或者说const是定义常变量的关键字

  2.const修饰的变量被称为常变量,具有常属性,但是本质上还是变量。

  3.保护被修饰的东西,防止被意外修改,增强程序的健壮性。

  4.提高程序的运行效率。编译器通常不为普通const常量分配存储空间,而是将他们保护在符号表中,使得它成为一个编译期间的常量,没有存储和读取内存的操作,使得它的运行效率也很高。

#include <stdio.h>
void test1()
{
	int n = 10;
	int m = 10;
    /* const放在*左边,修饰的是*p,表示的是指针指向的内容,不能通过指针来改变,
    但是指针变量本身是可以被修改的 */
	const int* p = &n;//const放在*的左边
	*p = 20;//err   *p不可改变(指针指向的内容不能被修改)
	p = &m; //ok    指针变量p可以修改
}
void test1()
{
	int n = 10;
	int m = 10;
    /* const放在*右边,修饰的是指针变量p本身,表示的是指针变量不可以被修改,
    但是指针指向的内容是可以被修改的 */
	int* const p = &n;//const放在*的右边
	*p = 20;//ok     //指针指向的内容可以被修改
	p = &m; //err    p不可改变(指针变量p不能被修改)
}
int main()
{
	test1();//const放在*的右边
	test2();//const放在*的左边
	return 0;
}

 

五. 指针运算

5.1 指针-指针

#include <stdio.h>
int my_strlen(char* s)
{
	//指针-指针
	char* p = s;
	while (*p != '\0')
		p++;
	return p - s;  //返回结束指针地址-开始指针地址 的差值     得到的就是指针之间元素的个数
	
}
int main()
{
	printf("%d\n", my_strlen("abc"));
	return 0;
}

两个指针相减,得到的就是指针之间的元素个数。指针s是字符串首地址,第一次p=s,随着p++,p指向最后一个字符后面的位置。所以p-s,就得到字符串的个数。

5.2 指针的关系运算 

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int* p = &arr[0];
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	while (p < arr + sz) //指针的大小比较
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

 指针的比较,依赖于指针所指向的两个元素的相对位置,若指针p指向arr[i],指针q指向arr[j],p和q的结果由i和j的大小决定。 

 六. 野指针

6.1 野指针的成因

1.指针未初始化

2.指针越界访问

3.指针指向的空间释放

6.2 如何规避野指针

1.指针初始化

2.小心指针越界

3.指针变量不再使用,及时置NULL,指针使用之前检查有效性

4.避免返回局部变量的地址

七. 指针的使用和传址调用

 7.1 strlen的模拟实现

库函数strlen的功能是求字符串长度,统计的是字符串\0之前的字符个数。

库函数原型:

size_t strlen ( const char* str );

 模拟实现,从起始地址开始向后逐个字符的遍历,只要不是\0字符,计算器就+1,直到\0就停止。

int my_strlen(const char* s)
{
	int count = 0;
	while (*s != '\0')
	{
		count++;
		s++;
	}
	return count++;
}
int main()
{
	int len = my_strlen("abcdef");
	printf("%d\n", len);
	return 0;
}

7.2传值调用和传址调用

例如:写一个函数,交换两个变量的值

#include <stdio.h>
void Swap(int x, int y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	printf("交换之前a = %d b = %d\n", a, b);
	Swap(a, b);
	printf("交换之后a = %d b = %d\n", a, b);
}

 运行结果:

看的出来这两个值是没有交换的,因为传的是值,并不会改变main函数里面的a和b的值,所以用传值交换失败了。

但是传址呢?

#include <stdio.h>
void Swap(int *x, int *y)
{
	int tmp = 0;
	tmp = *x;
	*x = *y;
	*y = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	printf("交换之前a = %d b = %d\n", a, b);
	Swap(&a, &b);
	printf("交换之后a = %d b = %d\n", a, b);
}

运行结果:

看到这里是交换成功了,因为传地址后在函数那做出更改时会改变在main函数里的a和b所指向地址的值,因此传址交换是可以的。 

这种也叫做:传址调用。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值