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;
}
代码的运行结果如图:
传址调用可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量。所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用;如果函数内部要修改主调函数中的变量的值,就要用到传址调用。