指针作为C语言中十分重要的知识点,我们应该了解并掌握它。
正文开始:
什么是指针?什么是指针变量?
人类通过地址来寻找得到自己想要的东西,于是便有了地址,计算机中也是同理。在计算机中我们将已写出的代码存入内存中,它们在内存中会被编号,这就是内存编号,内存编号指的就是你所创建的代码在内存中的名字。当我们需要调用这些代码时,会由CPU通过地址总线传数据给内存,在内存拿到地址,再通过数据总线返回到CPU。
在计算机中我们把内存单元的编号称为地址,C语言中给地址起了新的名字叫:指针。
指针变量则指的是用来存放地址的变量。
#include <stdio.h>
int main()
{
int a = 10;
// 创建a变量的本质就是向内存中申请一块空间
return 0;
}
代码运行结果如图:
1 0x00000032FD8FF714
2 0x00000032FD8FF715
3 0x00000032FD8FF716
4 0x00000032FD8FF717
#include <stdio.h>
int main()
{
int a = 10;
&a;//取出a的地址
printf("%p\n", &a);
return 0;
}
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;//取出a的地址并存储到指针变量pa中
return 0;}
这里的pa就是指针变量,指针变量也是一种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址。
我们看到pa的类型是 int* ,我们该如何理解指针的类型呢?
int a = 10;
int * pa = &a;
那如果有一个char类型的
char ch = 'w';
pc = &ch;//pc 的类型怎么写呢?
正确答案是:char *
#include <stdio.h>
int main()
{
int a = 100;
int* pa = &a;
//pa是int *类型的指针变量
*pa = 0;
//*pa表示对pa进行解引用
printf("%d",a);
return 0;
}
#include <stdio.h>
//指针变量的⼤⼩取决于地址的⼤⼩
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
printf("%zd\n", sizeof(char *));
printf("%zd\n", sizeof(short *));
printf("%zd\n", sizeof(int *));
printf("%zd\n", sizeof(double *));
return 0;
}
代码运行结果如图:
在VS里,x86就是32位环境。
//代码1
#include <stdio.h>
int main()
{
int n = 0x11223344;
int *pi = &n;
*pi = 0;
return 0;
}
//代码2
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
*pc = 0;
return 0;
}
代码1结果为:
代码2结果为:
#include <stdio.h>
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
指针类型
int* - 整形指针
char* - 字符指针
short* - 指向短整型的指针
float* - 指向浮点型的指针
......
-----------------------
void* - 无具体类型的指针
我们都知道,int类型的数据需要靠int*类型的指针来接收,但当我们不小心写错了呢?
#include <stdio.h>
int main()
{
int a = 0;
char* p = &a;
return 0;
}
编译器会报出警告!
如图:
#include <stdio.h>
int main()
{
int a = 0;
//char* p = &a;
int* p=&a;
return 0;
}
这里就没有显示报错:
2.将p改为void* 类型
#include <stdio.h>
int main()
{
int a = 0;
//char* p = &a;
void* p=&a;
return 0;
}
这里也没有显示报错:
当我们同时给void* 类型的指针传递两个地址时,它仍然不会报错
int main()
{
int a = 0;
float f = 0.0f;
void* v = &a;
v = &f;
return 0;
}
但是当我们用char* 指针来去整形的地址时,或用int* 来取float的地址时,编译器会报错。
int main()
{
int a = 0.0;
float f = 0.0f;
char* p = &a;
int* p2 = &f;
return 0;
}
报错如下:
int main()
{
int a = 10;
void* p = &a;
*p = 20;
return 0;
}
报错的原因是:表达式必须是可修改的左值
这就证明了:void* 类型的指针不能直接解引用的运算。
int main()
{
int a = 10;
void* p = &a;
*p = 20;
p = p + 1;
return 0;
}
这次报错的原因的:表达式必须是指向完整对象类型的指针。
#include <stdio.h>
int main()
{
int m = 0;
m = 20;//m是可以修改的
const int n = 0;
n = 20;//n是不能被修改的
return 0;
}
不加const前,指针不报错。
加了const后,指针报错。
为什么呢?
这是因为被const修饰后,n具有了常属性(常量属性,无法被修改的属性)。
那既然n具有了常属性,那么n现在是不是变成常量了呢?
这里我们创建一个数组,我们知道,在C语言中,数组里只能存放常量,我们再将n放进去,看结果。
从上图我们可以知道,虽然n具有了常属性,但是仍然不是常量,
我们可以把具有常属性的变量称为常变量。
但是如果我们还是想修改n的值该怎么办呢?
这里我们就可以运用上面的解引用来进行修改。
#include <stdio.h>
int main()
{
const int n = 10;
//n = 20;//err
int* p = &n;
*p = 20;
printf("%d", n);
return 0;
}
int main()
{
int a = 10;//&a - 0x0012ff40
int b = 20;//&a - 0x0012ff44
int*const p = &a;
p = &b;
return 0;
}
当const放在*右边时,p无法指向其他变量。
但是可以通过解引用改变指针变量指向的内容。
#include <stdio.h>
int main()
{
int a = 10;//&a - 0x0012ff40
int b = 20;//&a - 0x0012ff44
int*const p = &a;
//p = &b;//err
*p = 200;
printf("%d", *p);
return 0;
}
总结:
#include <stdio.h>
int main()
{
int a = 10;//&a - 0x0012ff40
int b = 20;//&a - 0x0012ff44
int*const p = &a;
//p = &b;//err
*p = 200;
printf("%d", *p);
//const修饰指针变量时,放在*的右边
//const限制的是指针变量本身,指针变量无法指向其他变量
//但是可以通过指针变量,修改指针变量指向的内容
return 0;
}
那么当const放在 * 左边又会发生什么呢?
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int const* p = &a;
p = &b;
printf("%d", *p);
return 0;
}
由图我们可以看到,当const放在*左边时,指针变量是可以改变的。
但当const放在 * 右边时就会出错。
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int const* p = &a;
//p = &b;
*p = 22;
printf("%d", *p);
return 0;
}
但是*p无法改变。
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int const* p = &a;
//p = &b;
//*p = 22;
printf("%d", *p);
//const修饰指针变量时,当const放在*左边
//const修饰的是指针变量指向的值,也就是*p,不能通过指针来修改
//但是可以修改指针变量的指向
return 0;
}
结论:
指针运算
例如,我们来打印一个数组中的所有元素
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
打印结果如图:
现在我们使用指针的方法打印一个数组中的所有元素
#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]);
for (int i = 0; i < sz; i++)
{
printf("%d ", *p);
p++;
}
return 0;
}
结果如图:
这里的&arr[0]指的是首元素的地址,因为p是int*的指针,所以+1是一次性跳过四个字节,就指向arr[1],这样依次增加,就能得到整个数组的元素了。
由1.指针+-整数我们得到了指针2,那么指针-指针我们又会得到什么呢?
答案是:整数
我们可以将指针-指针类比为日期-日期,日期-日期得到的是中间的天数,那么指针-指针得到的就是两个指针之间的元素。
我们这里写出一个指针相间的代码
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%zd\n", &arr[9] - &arr[0]);
printf("%zd", &arr[0] - &arr[9]);
return 0;
}
答案为下图,但是指针相减是不可能为负数的,于是两指针相减得到的是元素个数的绝对值!
我们在这里需要注意的是指针-指针的前提条件一定是:两个指针指向了同一块空间!当两个指针不指向同一空间,也就是指针类型不同时,编译器会报错。
我们依然用数组来写出指针-指针
//指针-指针
#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;
}
我们用char* p来存放s相加后的地址,两者相减就等于最后的地址-开头的地址,所以返回值为3。
3.指针的关系运算
//指针的关系运算
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
while(p<arr+sz) //指针的⼤⼩⽐较
//arr+sz是首元素的地址+数组的大小
{
printf("%d ", *p);
p++;
}
return 0;
}
指针的关系运算:
指针和指针比较大小
数组和数组比较大小
野指针
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
printf("%d",*p);
return 0;
}
编译器报错
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = &arr[0];
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
#include <stdio.h>
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
如何规避野指针
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#include <stdio.h>
int main()
{
int num = 10;
int*p1 = #
int*p2 = NULL;
return 0;
}
int main()
{
int arr[10] = {1,2,3,4,5,67,7,8,9,10};
int *p = &arr[0];
for(i=0; i<10; i++)
{
*(p++) = i;
}
//此时p已经越界了,可以把p置为NULL
p = NULL;
//下次使⽤的时候,判断p不为NULL的时候再使⽤
//...
p = &arr[0];//重新让p获得地址
if(p != NULL) //判断
{
//...
}
return 0;
}
这样使用让代码更安全,编译器不报错。
assert(p != NULL);
#include <assert.h>
#include <stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
assert(n > 3);
return 0;
}
断言成功,系统出错。
#define NDEBUG
#include <assert.h>
size_t strlen ( const char * str );
int my_strlen(const char * str)
{
int count = 0;
assert(str);
while(*str)
{
count++;
str++;
}
return count;
}
int main()
{
int len = my_strlen("abcdef");
printf("%d\n", len);
return 0;
}
运行成功
加const的作用是,不允许修改*str的值。
#include <stdio.h>
void Swap1(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);
Swap1(a, b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
#include <stdio.h>
void Swap2(int*px, int*py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
Swap1(&a, &b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
结果如下:
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n", &arr[0]);
printf("arr = %p\n", arr);
return 0;
}
输出结果:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", sizeof(arr));
return 0;
}
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n", &arr[0]);
printf("arr = %p\n", arr);
printf("&arr = %p\n", &arr);
return 0;
}
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n", &arr[0]);
printf("&arr[0]+1 = %p\n", &arr[0]+1);
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr = %p\n", &arr);
printf("&arr+1 = %p\n", &arr+1);
return 0;
}
#include <stdio.h>
int main()
{
int arr[10] = {0};
//输⼊
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
//输⼊
int* p = arr;
for(i=0; i<sz; i++)
{
scanf("%d", p+i);
//p+i = &arr[i]
//地址不断加加
//scanf("%d", arr+i);//也可以这样写
}
//输出
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));
//p+i是地址
//对每个地址进行解引用,可以找到对应元素
}
return 0;
}
测试答案如下:
正确!