在学习C语言的时候我们的老师经常会告诉我们指针是C语言的魅力所在,当你能很好
的使用指针的时候,你的C语言一定很不错,进入介绍之前,我们先初步了解有哪些C
语言常用的指针,以及目录。
下面我们主要围绕这几个方向对指针进行解析:
目录
一、指针是什么
指针:
1.指针是内存中最小单元的编号,也就是地址
2.我们口头中所说的指针,通常指的是指针变量,是用来存放地址的
我们将下图比作内存(当然内存一般是从先使用高地处的内存,在使用低地址处的内存)
如果需要取出某个变量对应的地址,那么我们需要的是使用 &变量名,取出的就是这个变量所在
内存中的地址
例如:
#include<stdio.h>
int main()
{
int num = 0;
int* p = #//取出num的地址,用int*类型的指针进行接收, p所指向的那块空间,就是num的内存地址
return 0;
}
当然指针类型也都会有自己的大小,在32位平台下指针的大小是4个字节,在64位平台
下指针的大小是8个字节,感兴趣的小伙伴可以使用 printf("%d\n",sizeof(int*));去尝试
一下
总结:
当然简单来说,指针就是地址,我们平时所说的指针大多数是指针变量,而指针变量是
用来存放地址的是用来存放地址的,
二、指针和指针类型
在通常的变量中,我们所熟知的变量有:
char
short
int
long
float
double
他们对应的指针类型为
char* pc = NULL;
short* ps = NULL;
int* pi = NULL;
long * pl = NULL;
float* pf = NULL;
duoble* pl = NULL:
NULL为空指针防止指针因为没有初始化而变成野指针
当然指针的类型,就是原有的 ( 类型 * )所构成的就是指针的类型
这些的区分,主要是为了不同类型指针的存放问题,
2.1.指针的大小以及对其解引用访问的内存
在这里有些小伙伴可能就要问了,我们创建那么多指针的用途是什么,每个指针在相同
平台下所占的内存相同,为什么不去创建一个共用的指针,这当然是因为不同的数据在
向内存中进行存储的过程中存储的方式可能不同,对指针进行解引用访问的空间不同,
对于char* 类型的指针,虽然指针的大小在32位平台下是4个字节,但是对这类型的指
针进行解引用的时候,一次只能访问一个 char 类型的内存大小:一个字节,同理对int*
类型的指针进行解引用一次访问4个字节的内存大小。double*一次访问8个字节的内
存,但是这并不妨碍这每一个类型的指针自身的大小是4个字节。
例如:
#include<stdio.h>
int main()
{
int a = 10;
int* pi = &a;
printf("%p\n",&a);
printf("%p\n",pi);
printf("%p\n",pi+1);
return 0;
}
我们可以看到对指针或者对应的内存都是指向同一块空间的,对所指向的内存+1就相当
于向后移动一个int类型的空间大小(4个字节)图中是以16进制进行打印的
2.2.对指针解引用操作
当我们对一个指针进行解引用 的时候,得到的结果,就是原来存放对应地址的那个变量
例如:
可以看出如果我们对指针变量存放的地址进行解引用得到的就是原来的变量 a
总结:
指针的类型决定的不是指针的大小而是指针在解引用时所能访问的空
间,指针的大小在不同平台下也是不同的
三、 野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
我们在使用指针的时候应当避免野指针的出现,因为他可能会使我们写出的程序出现一
些难以预料的BUG
以下这些情况会导致野指针的出现
1.指针未初始化
2.指针的越界访问
3.指针指向的空间内存被释放
3.1指针未初始化:
我们写了一个代码
int* p;
*p = 10;
这个就是一种对p未进行初始化,不知道p所指向的空间,对这个空间贸然进行修改,他
可能访问到我们已经创建的变量的空间,并对这个变量进行了修改,使程序出现我们不
可预料的BUG。
3.2指针的越界访问:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* pi = arr;
int i = 0;
for (i = 0; i <= 11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(pi+i) = i;
}
return 0;
}
当访问前10地址的时候,都是在访问arr数组中的地址,访问到第11的地址的时候,访
问的就可能是,在给arr数组开辟空间前,创建的变量的地址,如果对其进行修改,修
改的可能就是这个变量
3.3指针所指向的内存被释放:
如上图,这个结果看似没有问题,但是如果我们在这之间去使用一部分内存呢?.
可以明显看出,当释放了空间之后,原来的地址内存可以被使用,这就导致了,我们在
进行访问的时候,内存里面所保留的数据随着我们的使用而改变。如果内存没有被释
放,就不会出现上述情况(上述所有测试均在VS2019下,编译器不同可能结果也会不同)
3.4.如何避免写出野指针
当然如果想有效的避免野指针的使用,可以在使用指针的时候记住以下几点:
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放即使置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
四、指针的运算
4.1.指针 +- 整数
指针+-整数 表示的是向后向前跳过多少个(类型)字节大小的空间,
例如:
可以看出 在数组中 pi+1 访问的就是对于arr[0]中的下一个元素 arr[1]。
五、 指针和数组
当然在进行指针和数组的讲解之前,我们先要了解一个东西,就是数组名称的含义,通
常我们所创建的变量在取出地址的时候都要在变量前加上 & 符合,但是在去数组内容的
时候我们给的是 arr取出的就是首元素的地址
例如:
可以看到我们所取出的地址都是同一块内存
这就是arr表示的是首元素的地址,当然是 sizeof(arr)(特例) 中arr表示的是整个数组的大小,
当然,这也就有了我们在指针 +-整数的赋值操作
六、 二级指针
在学习完上述指针之后,我们就可以用上述创建指针(一级指针)的方式去创建一个二
级指针,当然这里我们还是先介绍二级指针的含义:
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
这就是 二级指针,
通俗来将,就是二级指针是用来存放一级指针变量地址的指针
例如:
ppi 中 存放的就是一级指针变量的地址
当我们对 ppi进行解引用是访问的就是 pi
七. 指针数组
简单来说,指针数组的本质是存放指针的数组
写法如下:
int* arr[5];
这样我们就创建了一个指针数组,数组中有五个元素,每个元素的类型是 int*
仿照上述,我们也可以创建一个,char*类型的指针数组
char* arr[5];
这个数组有5个元素,每个元素是char*类型的
八、初识结构体
该内容主要包括:
结构体类型的声明
结构体初始化
结构体成员访问
结构体传参
8.1结构体的声明和初始化
例如:
struct Stu//结构体的类型
{
char name[10];//姓名
int age;//年龄
};
#include<stdio.h>
int main()
{
struct Stu s = { "zhangsan",20 };// struct Stu 是类型 s 是变量名
return 0;
}
从图中我们可以看到 struct Stu 是结构体的类型 后面用这个类型创建的变量 s 才是变量
当然这个s是我们创建的局部变量的名称,并且我们对 S 进行了初始化
我们也可以用这个结构体创建一个全局变量
struct Stu//结构体的类型
{
char name[10];//姓名
int age;//年龄
} s1,s2;// s1 和 s2 是全局变量
#include<stdio.h>
int main()
{
struct Stu s = { "zhangsan",20 };// struct Stu 是类型 s 是变量名
struct Stu s1 = { "lisi",20 };
struct Stu s2 = { "wangwu",20 };
return 0;
}
这里的S1 和 S2是这个结构体类型下的全局变量
当然结构体的成员类型可以是:变量、数组、指针,其他结构体。
8.2.结构体成员的访问
在创建结构体变量的时候,我们可以看出结构体内含有多种变量的类型,那么我们需要
如何去访问结构体里面的内容呢?
可以看看下面这个代码:
可以看到,如果是指针的形式,我们可以使用 ->去访问结构体里面的元素,如果是正
常的结构体那么使用 . 就可以访问里面的元素
8.3.结构体传参
例如:
struct Stu
{
char name[10];
int age;
}s1,s2;
void print1(struct Stu st1)
{
printf("%s\n", st1.name);
printf("%d\n", st1.age);
}
void print2(struct Stu* st2)
{
printf("%s\n", st2->name);
printf("%d\n", st2->age);
}
#include<stdio.h>
int main()
{
struct Stu s = { "zhangsan",20 };
struct Stu s1 = { "lisi",20 };
struct Stu s2 = { "wangwu",20 };
print1(s); //传入结构体
print2(&s1);// 传入结构体的地址
return 0;
}