1.指针概述
内存
内存含义:
存储器:计算机的组成中,用来存储程序和数据,辅助CPU进行运算处理的重要部分。
内存:内部存储器,暂存程序/数据——掉电丢失 SRAM,DRAM,DDR,DDR2,DDR3。
外存:外部存储器。长时间保存程序/数据——掉电不丢失ROM,ERRROM,FLASH(NAND,NOR),硬盘,光盘。
内存是沟通CPU与硬盘的桥梁:
1.暂存CPU中的运算数据 2.暂存与硬盘等外部存储器交换的数据。
物理存储器和存储地址空间
有关内存的两个概念:物理存储器和存储地址空间。
物理存储器:实际存在的具体存储器芯片。
1.主板上装插的内存条 2.显示卡上显示的RAM芯片 3.各种适配卡上的RAM芯片和ROM芯片
存储地址空间:简单来说就是对存储器编码的范围。我们在软件上常说的内存就是指这一层含义
1.编码:对每个物理存储单元(一个字节)分配一个号码
2.寻址:可以根据分配的号码找到相应的存储单元,完成数据的读写
内存地址
1.将内存抽象成一个很大的一堆字符数组。
2.编码就是对内存的每一个字节分配一个32位或64位的编号(与32位或者64位处理器相关)。
3.这个内存编号我们称之为内存地址:
char:占一个字节分配一个地址 int:占4个字节分配4个地址 float,struct,函数,数组同理
指针和指针变量
内存区的每一个字节都有一个编号,这就是地址。如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号)。指针的实质就是内存地址,指针是内存单元的编号,指针变量就是存放地址的变量。
通常我们叙述时会把指针变量简称为指针。
2.指针基础知识
指针变量的定义和使用
1.指针是一种数据类型,指针变量是一种变量
2.指针变量指向谁,就把谁的地址赋值给该指针变量
3.” * “操作符操作的是指针变量指向的内存空间
总之:指针是内存中最小单元的编号,也就是地址。平时口语中说的指针,通常指的就是指针变量,是用来存放内存地址的变量。即指针就是地址,口语中的指针通常指的就是指针变量
#include <stdio.h>
int main()
{
int a = 0;
char b = 100;
printf("%p, %p\n", &a, &b); //打印a, b的地址
//int*代表一种数据类型,int*指针类型,p才是变量名
//定义了一个指针类型的变量,可以指向一个int类型变量的地址
int *p;
p = &a;//将a的地址赋值给变量p,p也是一个变量,值是一个内存地址编号
printf("%d\n", *p);//p指向了a的地址,*p就是a的值
char *p1 = &b;
printf("%c\n", *p1);//*p1指向了b的地址,*p1就是b的值
return 0;
}
注意:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里而在CPU里,所以是没有地址的
指针类型不能混用的原因:
结论1:指针类型决定了指针在被解引用的时候访问几个字节,如果是int*的指针,解引用访问4个字节,如果是char*的指针,解引用访问1个字节。所以,指针类型的不同在存放时不会产生问题,但不同的指针类型大小决定了解引用时访问几个字节。
结论2:指针的类型决定了指针+-1操作的时候,跳过几个字节,即决定了步长。
int* 和 float* 指针不能混用的原因:
虽然二者访问的变量大小都是4个字节,但int* 访问的是整型数据,是以整型的存储方式存储在内存中的,float* 访问的是浮点型数据,是以浮点型的数据存储在内存中的。
3.野指针和空指针
野指针和空指针的定义
野指针的定义是指针变量指向一个未知的空间,指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,64位为8字节),但是任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是不可控的(操作系统不允许操作此指针指向的内存区域)。所以,野指针不会直接引发错误,即野指针不解引用是不会造成危险的,操作野指针指向的内存区域才会出问题。但是野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中可以把NULL赋值给此指针,空指针是指内存地址编号为0的空间。
如何规避野指针
1.指针初始化 2.小心指针越界 3.指针指向空间释放即时置为NULL 4.避免返回局部变量的地址
5.指针使用之前检查有效性
#include <stdio.h>
//使用之前检查指针有效性
int main()
{
int *p = NULL;
int a = 10;
p = &a;
if(p != NULL)
{
*p = 20;
}
return 0;
}
4.万能指针与const修饰的指针变量
万能指针
void*指针可以指向任意变量的内存空间:在内存中 void*占四个字节大小,因此万能指针可以接收任意类型变量的内存地址,但最好将其强制转换为void*,也可以通过万能指针修改变量的值,但是修改变量的值时需要找到变量对应的指针类型。
int main()
{
int a = 10;
void* p = NULL;
p = (void*)&a; //指向变量时,最好转换为void *
//使用指针变量修改变量值时,转换为int *
*((int*)p) = 11;
printf("a = %d\n", a);
return 0;
}
const修饰指针变量
1.const修饰普通变量变为常量:可以通过指针间接修改常量的值
2.const修饰指针类型:可以修改指针变量的值,但不可以修改指针变量指向内存空间的值
3.const修饰指针变量:可以修改指针变量指向内存空间的值,不可以修改指针变量的值
4.const修饰指针类型修饰指针变量:只读指针 只能通过二级指针来修改
5、二级指针
一级指针用来存放变量的地址,二级指针则用来存放一级指针变量的地址
一级指针+偏移量相当于跳过了偏移量个元素,二级指针+偏移量相当于跳过了偏移量个一级指针的大小
二级指针的运算:
1.*ppa通过对ppa的地址进行解引用,这样找到的是pa,*ppa其实访问的就是pa.
2.**ppa先通过*ppa找到pa,然后对pa进行解引用操作,那找到的就是a.
结构体
1.结构体类型的声明
.结构的基础知识:
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
2.结构的声明:
typedef struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}Stu;//分号不能丢
3.结构成员的类型
结构成员可以是标量,数组,指针甚至是其他结构体
4.结构体变量的定义和初始化
struct Peo
{
char name[20];
char tele[12];
char sex[5];
int high;
};
struct St
{
struct Peo p;//结构体嵌套结构体
int num;
float f;
};
int main()
{
struct Peo p1 = {"张三", "15596668862", "男", 181};//结构体变量的创建
struct St s = { {"李四", "15596668888", "女", 166}, 100, 3.14f};
printf("%s %s %s %d\n", p1.name, p1.tele, p1.sex, p1.high);
printf("%s %s %s %d %d %f\n", s.p.name, s.p.tele, s.p.sex, s.p.high, s.num, s.f);
return 0;
}
struct Peo
{
char name[20];
char tele[12];
char sex[5];
int high;
};
struct St
{
struct Peo p;
int num;
float f;
};
void print1(struct Peo p)
{
printf("%s %s %s %d\n", p.name, p.tele, p.sex, p.high);//结构体变量.成员变量
}
void print2(struct Peo* sp)
{
printf("%s %s %s %d\n", sp->name, sp->tele, sp->sex, sp->high);//结构体指针->成员变量
}
int main()
{
struct Peo p1 = {"张三", "15596668862", "男", 181};//结构体变量的创建
struct St s = { {"lisi", "15596668888", "女", 166}, 100, 3.14f};
printf("%s %s %s %d\n", p1.name, p1.tele, p1.sex, p1.high);
printf("%s %s %s %d %d %f\n", s.p.name, s.p.tele, s.p.sex, s.p.high, s.num, s.f);
print1(p1);
print2(&p1);
return 0;
}
上面的print1和print2函数哪个好些?
答:优选print2函数.
原因:函数传参的时候参数是需要压栈的,如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销过大,会导致性能的下降.