文章目录
引入
你问我C语言精髓是什么,那包是指针系列最精彩呀,本文介绍了指针部分最基础的内容
内存和地址
内存
内存划分为一个个小的内存单元,一个内存单元的大小是一个字节,每一个字节都有一个专属的编号,内存单元的编号又叫地址
当电脑的CPU在内存上读取数据时,通过内存单元的地址来找到数据并读取
存在如下理解:内存单元的编号 = 地址 =指针
编址
每一个内存单元都有一个特殊的编号,这些编号记录在哪里呢?实际上,计算机中的编址,并不是把每个字节的地址记录下来,⽽是通过硬件设计完成的,就像是钢琴上不需要写“剁、来、咪、发、唆、拉、西”这样的信息,但演奏者照样能够准确找到每⼀个琴弦的每⼀个位置,?因为制造商已经在乐器硬件层⾯上设计好了,并且所有的演奏者都知道。本质是⼀种约定出来的共识
指针变量和地址
取地址操作符(&)
思考:创建变量的本质是什么?
事实上就是向内存申请空间,申请了空间,要怎么得到这块空间的地址呢? 哎~取地址操作符这不就来了么。
#include<stdio.h>
int main()
{
int a = 10;
printf("%p", &a);
return 0;
}
这样一个代码就可以打印出a
的地址,我们看到在打印时,用到了一个操作符&
这里可不是按位与,而是取地址操作符,简单理解就是可以取得变量的地址
int
类型的变量在内存中占用四个字节的空间,每一个字节都有一个专门的地址,这里取a
的地址取得的是第一个字节的地址
解引用操作符(*)
指针变量
我们获取到一个变量的地址后要存储指针变量中
int main()
{
int a = 10;
int* pa = &a;
return 0;
}
指针变量和其他任何一种变量都一样,就像是整形变量是用来存一整数,指针变量就是用来存一指针,指针又叫地址,所以我也想为啥不直接叫地址变量,就是想告诉读者,指针变量没有啥特别的
理解指针变量
我们看到指针变量是包含三个部分的,分别是int
*
pa
,一颗星表示这里的变量是指针变量,星左边的是指针变量的类型,星的右边是指针变量的变量名
解引用操作符
取到了一个变量的地址,要怎们通过地址来使用这个变量呢?那包是解引用操作符啊
int main()
{
int a = 100;
int* pa = &a;
*pa = 0;
return 0;
}
*pa
翻译就是:通过pa
里存储的地址,找到这个地址对应的变量
指针变量的大小
指针变量就是用来存储一个地址的变量,要想弄明白指针变量有多大,就要清楚,一个地址有多大
我们假设一个32位机器有三十二根地址总线,每根地址线都能发出电信号,这些电信号转化为数字信号就是0或1,我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储。同理,64位机器产生的地址就要8字节来存储
指针变量的类型和意义
看完指针变量的大小我们知道,不管是什么类型的指针变量大小都是4或8个字节,那么指针变量的类型有啥用呢?
一句话概括指针变量类型的意义就是,决定了访问变量时的步长,我们通过下面的代码来理解
指针的解引用
int main()
{
int n = 0x11223344;
int* pi = &n;
*pi = 0;
/*char* pc = (char*)&n;
*pc = 0;*/
return 0;
}
int main()
{
int n = 0x11223344;
/*int* pi = &n;
*pi = 0;*/
char* pc = (char*)&n;
*pc = 0;
return 0;
}
直接看图是不是能瞬间理解指针的类型决定指针访问时的步长的含义?
代码一,我们把n
的地址存放在一个整形指针中,然后通过指针来修改n
的值,结果是正确的修改
代码二,我们把n
的地址存放在一个字符型指针中,发现只改了一个字节的值
这个结果就说明,指针便变量的类型决定了指针的访问时时的步长
指针 ± 整数
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;
}
我们可以看出,char*
类型的指针变量+1跳过1个字节,int*
类型的指针变量+1跳过了4个字节。
这就是指针变量的类型差异带来的变化。指针+1,其实跳过1个指针指向的元素。指针可以+1,那也可以-1。
结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。
指针运算
指针的基本运算有三种,分别是:
- 指针±整数
- 指针-指针
- 指针的关系运算
指针加减±整数
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;
}
这里实际上是因为数组在内存中连续存放,所以可以通过这种方式来访问
指针-指针
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;
}
当两个指针指向同一块区域,可以通过指针-指针的方式来计算两指针之间的元素个数
指针的关系运算
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;
}
野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
大白话就是说:你没有权限访问,但是访问了
int main()
{
int* p;
*p = 20;
return 0;
}
指针变量p
中是没有存任何地址的,再解引用赋值,就导致了野指针的使用
#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;
}
这里对数组进行了越界访问,超出了p
应该访问的范围,导致野指针的使用
int* func()
{
int n = 100;
return &n;
}
int main()
{
int *p = func();
printf("%d ", *p);
}
当调用完func
函数,变量n
已经被销毁,此时再去使用n
原本地址就是非法访问,导致野指针的使用
规避野指针
使用指针前初始化
int main()
{
int num = 10;
int*p1 = #
int*p2 = NULL;
return 0;
}
创建的指针变量*p2
没有指向,就要给其赋值为NULL
小心指针越界
使用指针访问数组时,要看清楚指针是否越界访问
及时置空,使用前检测指针有效性
当指针变量不再使用后,要将指针变量及时置空,使用指针变量之前,也要检查指针是否为空
assert断言
assert.h
头⽂件定义了宏assert()
,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。
//#define NDEBUG
#include<assert.h>
void func(int* p, int sz)
{
assert(p);
for (int i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
func(arr, sz);
return 0;
}
就是检测传来的指针变量是否为空指针,define NDEBUG
相当于一个开关,来控制断言是否生效
总结
以上就是指针部分的一些基本概念,常识
感谢阅读 ^ - ^
完
标签:arr,return,变量,int,C语言,修仙,地址,指针 From: https://blog.csdn.net/Zach_yuan/article/details/142280850