内存和地址
讲解这个之前要先明确指针的概念,举个例子,在居民楼中,如果你要找个人并且有他的门牌号,是不是就能快速找到他住的房间,对应到计算机中,cpu处理的数据是需要在内存中读取的,内存的管理也是同房间号一样划分成一个个内存单元,每个内存单元就和门牌号一样有一个编号,生活中我们把门牌号叫做地址,计算机中内存单元的编号也叫做地址,c语言中给地址取了一个新名字:指针。
所以我们可以理解为
内存单元的编号=地址=指针
指针变量和地址
取地址操作符(&)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main() {
int a = 10;
printf("%p", &a);//输出a的地址
return 0;
}
看一下运行结果
但int a毫无疑问是占4个字节的,&a所输出的是a所占4个字节中地址较小的字节
指针变量
我们通过上面的&a得到的地址是一个数值,那这样的数值就可以放在指针中方便使用
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main() {
int a = 10;
int* q = &a;//取出a的地址存放在指针q中
return 0;
}
使用指针变量就是一种存放地址的变量,存放在其中的值都会被理解为地址
指针的类型
int a = 10;
int* q = &a;
q左边写的是int*,其中*代表q是一个指针变量,*前面的int是说明q指向的是一个int类型的对象
解引用操作符(*)
将地址保存进指针之后,如何取出来使用呢,
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main() {
int a = 10;
int* q = &a;
*q = 0;
return 0;
}
*q的意思就是通过q中存放的地址,找到指向的空间,*q其实就是a变量了,使用*q=0,就是把a变量改成了0,这样对a的修改就多了一种途径,能够更灵活的使用。
指针变量的大小
在32位平台地址就是32bit,指针变量大小为4个字节
在64位平台地址就是64bit,指针变量大小为8个字节
指针变量大小与类型无关,在相同平台下,大小都是相同的
指针变量类型的意义
指针的解引用
指针变量的类型虽然与大小无关,但它还是有意义的,它决定了对指针解引用的时候有多大的权限(一次能操作几个字符),如char*的指针解引用只能访问一个字节,int*的指针解引用就能访问4个字节
指针加减整数
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main() {
int a = 1;
char* q = (char*) & a;
int* w = &a;
printf("&a = %p\n",&a);
printf("&q = %p\n",&q);
printf("&q+1 = %p\n",&q+1);
printf("&w = %p\n",&w);
printf("&w+1 = %p\n",&w+1);
return 0;
}
运行结果如下
可以看到char*类型的指针变量+1跳过一个字节,int*的指针变量跳过了4个字节,这就是指针类型差异带来的变化
const修饰指针
如果希望指针变量不被修改
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main() {
int a = 1;//a是可修改的
const int w = 1;//w是不可修改的
return 0;
}
如果加上const再想修改w,程序就会直接报错
但如果绕过w,使用w的地址去修改w就可以
#include <stdio.h>
int main()
{
const int n = 0;
printf("n = %d\n", n);
int*p = &n;
*p = 20;
printf("n = %d\n", n);
return 0;
}
const修饰指针变量的时候
const如果放在*左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针改变,但指针变量本身的内容可变
const如果放在*右边,修饰的是指针变量本身,保证指针变量的内容不能修改,但指针指向的内容可以修改
指针运算
指针加减整数
因为数组在内存中是连续存放的,只要知道头元素的地址,就能找到后面的全部元素
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main() {
int arr[5] = {1,2,3,4,5};
}
下面是一个指针加减整数的例子
#define _CRT_SECURE_NO_WARNINGS 1
#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]);
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));//指针+整数
}
return 0;
}
可以看到循环正常输出,*(p+i)其实等同与arr【i】
指针减指针
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int my_strlen(const char* s){
const char* p = s;
while (*p != '\0')
p++;
//最终p指向了\0
//s还是指向字符串“abc”首元素a的地址
return p - s;//返回的就是\0前的元素个数
}
int main(){
printf("%zd\n", my_strlen("abc"));
return 0;
}
所以可知指针-指针的绝对值是指针和指针之间的元素个数(大地址减去小地址得到的是正数,小地址减去大地址得到的是负数 )
野指针
野指针就是指针指向的位置是不可知的
野指针成因
1.指针未初始化
#include <stdio.h>
int main(){
int *p;//局部变量指针未初始化为随机值
*p = 20;
return 0;
}
2. 指针越界访问
#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;
}
指针虽然可以指向未知的空间不会报错,但是你要是要操作那块空间就有可能会报错(因为越界访问了)
3. 指针指向的空间释放
#include <stdio.h>
int* test(){
int n = 100;
return &n;
}
int main(){
int*p = test();
printf("%d\n", *p);
return 0;
}
如此也能输出100
如何规避野指针
指针初始化
如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL.
初始化如下:
#include <stdio.h>
int main()
{
int num = 10;
int*p1 = #
int*p2 = NULL;
return 0;
}
注意指针是否越界
指针变量不再使用时,及时置NULL,指针使用之前检查有效性
assert断言
assert(p != NULL);
assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), assert() 不会产⽣任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误流 stderr 中写⼊⼀条错误信息,显⽰没有通过的表达式,以及包含这个表达式的⽂件名和行号。
使⽤ assert() 它不仅能⾃动标识⽂件和出问题的行号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问 题,不需要再做断⾔,就在 #include <assert.h> 语句的前⾯,定义⼀个宏 NDEBUG
#define NDEBUG
#include <assert.h>
如果程序⼜出现问题可以移除这条 #define NDBUG 指令,就重新启⽤了 assert() 语句。
指针的使用和传址调用
有没有什么问题是非指针不可的呢
#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;
}
看下结果