创作不易,希望各位大佬多多支持~
学习不易,希望大家保持乐观的心态哦~
我的主页:optimistic_chen
我的专栏:c语言
前言
经过之前一段时间的学习,我们终于来到C语言的第一座大山——指针
让我们一起来拿下它!!!
注:为了更好的连接之前的知识,欢迎大家访问:
浅浅理解C语言之数组大佬
揭示C语言操作符之神秘
文章目录
一、指针是什么?
1.概念
我们知道计算上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,为了高效准确的读取到数据,把内存划分为⼀个个的内存单元,每个内存单元也都有⼀个编号(这个编号就相当
于宿舍房间的⻔牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间,从而读取数据。这些编号我们就将其称为地址或者指针。
内存单元的编号 == 地址 == 指针
2.相关操作符(&、*)
&- - 取地址操作符,*- -解引用操作符,在揭示C语言操作符之神秘中都有过学习。还记得,他们属于一对“反义词”。
让我们用举例做到更好的理解:
由图可知:
int a=1;//&a为0x00000001
int b=2;//&b为0x00000002
int c=3;//&c为0x00000003
使用指针变量:
int*pa=&a;//把a的地址存起来
int*pb=&b;//把b的地址存起来
int*pc=&c;//把c的地址存起来
我们可以通过门牌号准确的找到对应同学,同理,我们也可以通过地址准确的找到对应变量。
printf("a=%db=%dc=%d", *pa, *pb, *pc);//通过*解引用地址找到对应的值
结果:
int a=1;
int b=2;
int c=3;
int*pa=&a;
int*pb=&b;
int*pc=&c;
printf("a=%db=%dc=%d", *pa, *pb, *pc);//a=1 b=2 c=3
3.指针变量
因为数据在内存中的地址称为指针,这个地址(指针)就存放在指针变量中,也就是说如果一个变量存储了一份数据的地址(指针),我们就称它为指针变量。
如何使用指针变量?
tybe*name
变量类型 * 变量名称
例如:
int *arr;//指向一个整形的指针
float *a;//指向一个单精度浮点数的指针
double *p1;//指向一个双精度浮点数的指针
同时,我们可以根据需求改变变量的赋值
*pa=4;
*pb=5;
*pc=6;
printf("a=%db=%dc=%d", *pa, *pb, *pc);//a=4 b=5 c=6
注:大家在写代码时小心使用指针的变量类型哦,否则会出现警告!!!
4.void*指针
在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为⽆具体类型的指针(或者叫泛型指
针),这种类型的指针可以⽤来接受任意类型地址。
也就是说:*任意类型的指针可以对 void 指针赋值
void*a;
int*b;
a=b;//这是被允许的
但是不能把void*指针赋值给任意指针类型,当然也不能直接对其解引用
void*a;
int*b;
b=a;//这是不被允许的
*a;//不能直接对void*解引用
5.const修饰
5.1const修饰变量
变量是可以修改的,但是如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作⽤。
#include <stdio.h>
int main()
{
int a = 0;
a = 20; //a是可以修改的
const int b = 0;
b = 20; //b是不能被修改的
printf("%d%d",a,b);
return 0;
}
显然,加上const后,编译器报错,所以变量不可被修改。
5.2const修饰指针变量
如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。
这样就打破了const的限制,这是不合理的,那接下来怎么做呢?
测试⽆const修饰的情况
int main()
{
int a = 10;
int b = 20;
int *p = &a;
*p = 20;
p = &b;
printf("%d%d",a,b);//a=20 b=20
}
测试const放在*的左边情况
int main()
{
int a = 10;
int b = 20;
const int *p = &a;
*p = 20;
p = &b;
printf("%d%d",a,b);//*p报错
}
测试const放在*的右边情况
int main()
{
int a = 10;
int b = 20;
int *const p = &a;
*p = 20;
p = &b;
printf("%d%d",a,b);//p报错
}
测试*的左右两边都有const
int main()
{
int a = 10;
int b = 20;
const int *const p = &a;
*p = 20;
p = &b;
printf("%d%d",a,b);//p和*p都报错
}
结论:
const如果放在的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本⾝的内容可变。
const如果放在的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。
二、指针运算
1.指针变量的大小
我们知道一个字节是8个bit位
那么在32位机器上,⼀个地址就是32个bit位,需要4个字节才能存储。
如果指针变量是⽤来存放地址的,那么指针变量的⼤⼩就得是4个字节的空间才可以。
同理,在64位机器,⼀个地址就是64个bit位,存储起来就需要8个字节的空间,指针变量的⼤⼩就是8个字节。
结论:
• 32位平台下地址是32个bit位,指针变量⼤⼩是4个字节
• 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节
• 注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。
2.指针±整数
代码示例:
#include <stdio.h>
int main()
{
int a = 10;
char arr[10] = "abcd";
int *p1 = &a;
char* p2 = &arr;
printf("&a=%p\n", &a);
printf("p1=%p\n", p1);
printf("p1+1=%p\n", p1+1);
printf("p2=%p\n", p2);
printf("p2+1%p\n", p2+1);
return 0;
}
代码运行结果如下:
我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
这就是指针变量的类型差异带来的变化。
结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)
例题:通过指针访问数组每个元素
首先我们知道数组在内存中是连续存储的(地址由低到高),其次一维数组的数组名就是首元素地址,所以我们只要首元素的地址就能找到数组所有元素的地址。
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = arr;//&arr[0]==arr
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);//数组元素个数
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i)); //p+i 这⾥就是指针+整数
}
return 0;
}
3.指针-指针
指针-指针其实是指在同一空间内,两个指针之间的元素个数
简单理解就是一个字符串的元素个数:
#include<stdio.h>
int my_strlen(char* a)
{
char* p = a;//数组名就是首元素地址
while (*p != '\0')
{
p++;
}
return p - a;
}
int main()
{
char a[10] = "abcd";
int len = my_strlen(a);
printf("%d", len);
return 0;
}
代码结果:
三、野指针
概念:指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
1.野指针的成因
1.1指针未初始化
int *p; //局部变量指针未初始化,默认为随机值
*p = 20;
1.2指针越界访问
int arr[10] = { 0 };
for (int i = 0; i < 11; i++)
{
//数组下标是0到9
printf("%d ", *(arr + i));//当指针指向的范围超出数组arr的范围时,p就是野指针
}
1.3指针指向的空间释放
首先我们要了解变量在不同位置的意义,学习函数时,我们知道在函数定义中的变量属于临时变量,离开函数定义的作用域后,系统为了提高代码效率就会销毁存放变量的空间。一旦销毁,系统就无法访问该空间,而通过指针我们还可以访问该空间,这就造成了冲突,所以出错,造成野指针。
#include <stdio.h>
int* test()
{
int n = 100;
return &n;//返回n的地址
}
int main()
{
int* p = test();//用p接受n的地址
printf("%d\n", *p);//打印出n的值
return 0;
}
2.解决方法
针对造成野指针的问题来解决
2.1指针初始化
int *p=NULL;
*p = 20;
2.2小心指针越界
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是
越界访问。
2.3避免返回局部变量的地址
如造成1.3中的例子
四、assert断言
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报
错终⽌运⾏。这个宏常常被称为“断⾔”。
assert()宏接受⼀个表达式作为参数。
如果该表达式为真(返回值⾮零), assert() 不会产⽣任何作⽤,程序继续运⾏。
如果该表达式为假(返回值为零), assert() 就会报错,在标准错误流 stderr 中写⼊⼀条错误信息,显⽰没有通过的表达式,以及包含这个表达式的⽂件名和⾏号。
代码示例:
int *p=NULL;
assert(p);//空指针是0,0为假,就会报错
如果已经确认程序没有问题,不需要再做断⾔,就在 #include <assert.h> 语句的前⾯,定义⼀个宏 NDEBUG 。
#define NDEBUG
#include <assert.h>
缺点就是因为引⼊了额外的检查,增加了程序的运⾏时间。
完结
指针学习尚未结束,尽情期待,下期更精彩哦~~~