目录
一、什么是指针
1.1、定义
1.2、取地址操作符(&)
1.3、指针变量和解引用操作符(*)
二、指针变量类型的意义
三、const修饰指针
3.1、const修饰变量
3.2、const修饰指针变量
3.2.1、const放在*的左边
3.2.2、 const放在*的右边
四、指针运算
4.1、指针+-整数
4.2、指针-指针
4.3、指针的关系运算
五、野指针
5.1、定义
5.2、成因
5.2.1、指针未初始化
5.2.2、指针越界访问
5.2.3、 指针操作超过所指向变量的生存期
六、传值调用和传址调用
一、什么是指针
1.1、定义
指针在C语言中也叫内存地址。那么什么是内存地址呢?以生活中的房间号为例,正因为生活中的这些房间号,我们才能快速找到要找的房间。而内存地址就相当于这些房间号,有了这些内存地址,CPU就能快速找到一个内存空间。
1.2、取地址操作符(&)
在C语⾔中,我们想要取出一个变量的地址,就要使用一种操作符:&。例如:
#include <stdio.h>
int main()
{
int a = 10;
printf("%p\n", &a);
return 0;
}
上述,我们先定义了一个整型变量a,并赋值为10,然后我们打印地址。
注:打印地址时,用%p。
结果如下:
1.3、指针变量和解引用操作符(*)
既然我们可以用&拿出一个地址,那么是否可以用什么东西来存储地址呢?答案是有的,这种东西就叫做指针变量。如下:
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;
return 0;
}
既然我们将地址保存了起来,那么未来一定是要使用的。但是我们要怎样才能使用呢?这个时候就要用到另一种操作符:“*”(解引用操作符)。
#include <stdio.h>
int main()
{
int a = 100;
int* pa = &a;
*pa = 10;
printf("*pa=%d\n", *pa);
printf("a=%d\n", a);
return 0;
}
结果如下:
可以看到,通过解引用操作符,我们将*pa赋值为10,而a最终的结果也是10,由此我们可以知道:可以通过改变指针变量的值,间接改变指针变量所指向的变量的值。
二、指针变量类型的意义
请看如下代码,观察其运行结果:
#include <stdio.h>
int main()
{
int a = 100;
int* pa = &a;
char* ch = &a;
printf("&a=%p\n", &a);
printf("pa=%p\n", pa);
printf("pa+1=%p\n", pa+1);
printf("ch=%p\n", ch);
printf("ch+1=%p\n", ch+1);
return 0;
}
其结果如下:
我们可以看出,char*类型的指针+1后只跳过一个字节,而int*类型的指针+1后跳过了4个字节。由此,我们可以知道:不同的指针类型,加上或减去同一个数后,跳过的字节数不同。
三、const修饰指针
3.1、const修饰变量
变量是可以修改的,但是如果在变量前加上const,则变量便不能被修改。
3.2、const修饰指针变量
⼀般来讲const修饰指针变量,可以放在*的左边,也可以放在*的右边,意义是不⼀样的。
3.2.1、const放在*的左边
#include <stdio.h>
int main()
{
int m = 10;
int n = 20;
int const* pa = &m;
*pa = 20;//不可行,报错
pa = &n;//可行
return 0;
}
上述代码说明:const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。
3.2.2、 const放在*的右边
#include <stdio.h>
int main()
{
int m = 10;
int n = 20;
int* const pa = &m;
*pa = 20;//可行
pa = &n;//不可行,报错
return 0;
}
上述代码说明:const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。
四、指针运算
4.1、指针+-整数
因为数组在内存中是连续存放的,所以只要知道第⼀个元素的地址,利用指针加减整数就能找到数组的所有元素。
#include <stdio.h>
int main()
{
int arr[6] = { 1,2,3,4,5,6 };
int* pa = &arr[0];
int sz = sizeof(arr) / sizeof(arr[0]);//通过sizeof获取数组长度
for (int i = 0; i < sz; i++) {
printf("%d ", *(pa + i));
}
return 0;
}
结果如下:
4.2、指针-指针
先说结论:指针-指针的绝对值是两个指针之间元素的个数。
由此,我们可以自行写出一个计算字符串长度的函数my_strlen:
#include <stdio.h>
size_t my_strlen(char* ch) {
char* strat = ch;
char* end = ch;
while (*end != '\0') {
end++;
}
return end - strat;
}
int main()
{
char arr[] = "abcdef";
int num = my_strlen(arr);
printf("%d\n", num);
return 0;
}
结果如下:
4.3、指针的关系运算
//指针的关系运算
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int sz = sizeof(arr)/sizeof(arr[0]);
while(p<arr+sz) //指针的⼤⼩⽐较
{
printf("%d ", *p);
p++;
}
return 0;
}
结果如下:
五、野指针
5.1、定义
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
5.2、成因
5.2.1、指针未初始化
#include <stdio.h>
int main()
{
int* p;//指针未初始化,默认为随机值
*p = 10;
return 0;
}
规避方法:初始化指针:如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL。 NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错。
5.2.2、指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = &arr[0];
int i = 0;
for (i = 0; i <= 11; i++){//指针越界访问
*(p++) = i;
}
return 0;
}
当指针指向的范围超出数组arr的范围时,p就是野指针。
5.2.3、 指针操作超过所指向变量的生存期
当指针执行的变量的声明周期已经结束时,如果指针仍然指向这块空间,就会使该指针成为野指针。
六、传值调用和传址调用
假如叫你写⼀个函数,交换两个整型变量的值,你可能会写出如下代码:
#include <stdio.h>
void swap(int x, int y) {
int c = x;
x = y;
y = c;
}
int main()
{
int a = 10;
int b = 20;
swap(a, b);
printf("a=%d\n", a);
printf("b=%d\n", b);
return 0;
}
结果如下:
然后你震惊地发现,a和b的值并没有互换,这是因为a、b和x、y的地址不同,所以虽然x和y的值互换了,但是并不影响a和b的值。这就是传值调用。而传址调用则不同:
#include <stdio.h>
void swap(int* x, int* y) {
int c = *x;
*x = *y;
*y = c;
}
int main()
{
int a = 10;
int b = 20;
swap(&a, &b);
printf("a=%d\n", a);
printf("b=%d\n", b);
return 0;
}
结果如下:
我们发现,这次a和b的值成功互换了,这是因为这次将a和b的地址传入了函数,函数通过地址间接操作main中的a和b,这就是传址调用。
未完待续......