指针(2)

1.void*指针

void*类型是一类特殊的指针类型,这一类型的指针可以接受任意类型的地址,但它不可以直接进行指针的+-整数和指针的解引用的运算。如图:

int main()
{
	int a = 0;
	void* pa = &a;
	printf("%d\n", *pa = 10);
	return 0;
}

当运行以上代码是会报错,如图:

 我们可以看出void*可以接受任何类型的地址,但不能直接解引用,如果想要使用,可以强制类型转换后再解引用。

2.const修饰指针
 

int main()
{
	int m = 0;
	m = 20;
	printf("%d\n", m);
	//const int n = 0;
	//n = 30;
	//printf("%d\n", n);
	return 0;
}

当执行以上代码时,我们可以看出m的值为20,发生了改变。当我们取消注释后,会产生报错,因为在int前面加上了const就代表n不允许被修改了,在语法上加上了限制,但n还是一个变量。
但如果我们执行下面代码,我们发现可以通过n的地址来修改n的值,简单的理解为我们把一个房间的门封住了,不让人进去住,但是那个人却打破了窗户,进去住。

int main()
{
	const int n = 0;
	int* pa = &n;
	*pa = 20;
	printf("%d\n", n);
	return 0;
}

上述的代码不符合语法,因此我们可以通过const修饰指针变量来避免这些问题。
const在修饰指针变量时有两种情况,第一种const如果放在*左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变,但指针变量本身的内容是可以改变的,如

int main()
{
	int n = 10;
	const int* pa = &n;  //与int const* pa一样
	int m = 20;
	printf("%p\n", pa);
	pa = &m;
	printf("%p\n", pa);
	return 0;
}

以上代码的结果为,可以看出指针变量的内容发生了改变。

 第二种情况是const放在*号的右边,修饰的是指针变量本身,保证了指针变量的内容不能改变,但指针指向的内容可以通过指针改变。如

int main()
{
	int n = 20;
	int* const pa = &n;
	int m = 30;
	pa = &m;
	return 0;
}

当运行以上代码时,会产生报错如图所示:

但当运行下面的代码时,会发现pa所指向的内容发生了改变。

int main()
{
	int n = 20;
	int* const pa = &n;
	int m = 30;
	printf("%d\n", *pa);
	*pa = m;
	printf("%d\n", *pa);
	return 0;
}

运行结果如图所示

3.指针的运算

3.1指针+-整数

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;  //数组名是首元素的地址,因此arr=&arr[0]
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));  //当i=0时,p指向的是arr首元素的地址,p+0指向的还是首元素的地址,*(p+0)就是首元素
	}							  //当i=1时,p+1指向的是第二个元素的地址,*(p+1)就是第二个元素
	return 0;
}

3.2指针-指针

int my_strlen(char* s) //接收的是地址
{
	char* p = s;  
	while (*p)
	{
		p++;
	}
	return p - s;
}

int main()
{
	printf("%d\n", my_strlen("abc"));
	return 0;
}    

指针-指针的绝对值是指针之间的元素个数。该运算的前提条件是两个指针指向同一块空间。

3.3指针的关系运算(指针比较大小)
 

int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	while (p < arr + sz) //arr的首元素地址+该数组的长度
	{
		printf("%d ", *p); //打印数组
		p++;
	}
	return 0;
}

4.野指针

野指针就是指针指向懂得位置是不可知的。
野指针的成因:指针未初始化、指针越界访问、指针指向的空间被释放。
指针未初始化是因为如果局部变量不初始化,变量的值是随机的。(静态区的变量不初始化默认值为0,如全局变量和静态变量)
指针指向的空间被释放,代码

int* test()
{
	int n = 100;  //变量n在该函数结束时,内存被释放
	return &n;
}

int main()
{
	int* p = test();
	printf("%d\n", *p); //p虽然记住了地址,但由于内存被释放,再访问的话就是非法访问
	return 0;
}

避免野指针产生的方法:指针初始化、小心指针越界、指针变量不再使用时,及时放置NULL,指针使用之前检查有效性、避免返回局部变量的地址。
指针初始化:若果明确知道指针指向哪里就直接赋值地址,不过不知道指针指向哪里,可以给指针赋值NULL。

5.传值调用和传址调用

让我们分析下面的代码:

void swap(int x, int y)
{
	int  tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);
	swap(a, b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

这是个传值调用

我们可以看出这个代码是让两个值进行交换,当我们运行时,结果如图:

我们可以发现两者的值并没有交换。
当我们调试时可以发现图1中,形参中的数发生了交换,但回到主函数是,a和b两者的值没有交换,如图2。

图1

图2

因此我们可以得出结论:实参传递给形参时,形参会单独创建一份临时的空间来接收实参,对形参的修改不影响实参。
如果我们想要实现交换的话,就要用到传址调用,如以下代码:

void swap(int* p1, int* p2)
{
	int tmp = 0;
	tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);
	swap(&a, &b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

代码的运行结果如图:

传址调用可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量。所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用;如果函数内部要修改主调函数中的变量的值,就要用到传址调用。

  • 47
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值