指针
指针的概念
1.指针初了解
*&与&*的意义
区别指针与指针变量:
- 指针:地址 p;是一个地址常量,一个常性值,不能+=1
指针变量:存放地址的变量 int * p ;
具有+=的能力:p+=1,此时指针p指向下一个存储单元的地址
int * p ; // * 是声明,声明P是一个整型的指针变量
*p ; // *与P结合表指向
指针的2个值:
- 1.存放某个变量的地址,用来操纵指针自身
- 2.解引用时就是某个变量本身,操纵指针指向
每一个16进制是4个二进制位,故8位16进制一共32位二进制,需要4个字节
1K=2^10 ; 1M=2^20 ; 1G=2^30 ;1T=2 ^40
所以 2^32=4G
2.星号 * 的用法:
3.指针的分类
(1)野指针:
- 定义指针变量,指向某一地址,但是未初始化,不知道该指针指向内存的哪一个位置点
int * p ;
(类似于 int a ;定义一个整形,未初始化,变量a中存放的是一个随机值)
(2)空指针:
int * p=nullptr ; //定义指针初始化为空指针
(3)失效指针:
- 程序在运行过程中,指针所指向的区域被系统回收回去,虽然指针仍然指向该区域,但是不能够使用,失效指针在动态分配内存时比较常见:
int* ip = (int*)malloc(sizeof(int) * 10); // free(ip);
释放ip 实际是将你 在堆区malloc 的空间还给系统,此时就会导致ip 指针变为失效指针,只有在整个程序结束时 ip 指针才会被释放
此时 ip 为失效指针(也叫失能指针)
当编译器调用主函数,给主函数分配一个栈帧,ip 指针初始化为空,调用GetArray函数,此时又分配一个栈帧给GetArray函数,在GetArray函数栈帧中定义了一个n初始化为10,又定义一个ar数组,大小为10;return时将ar的值放在一个临时空间tmp中,return 意味着此函数结束,系统分配的栈空间释放,返还给系统,系统可再次进行分配,ar指向的此空间存在,但是对于当前程序来说已经交付给系统,回到主函数,ip也指向此内存,但是打印此内存的值都是随机值。这种情况下 ip 叫做失效指针
若要 ip 不为失效指针只需要将ar设置为静态数组即可,此时ar数组存放在数据区,数据区只有在整个程序结束时才会被系统回收
static int ar[n] = { 12,23,34,45,56,67,78,89,90,100 };
#include<stdio.h>
#include<assert.h>
int* GetArray()
{
const int n = 10;
int ar[n] = { 12,23,34,45,56,67,78,89,90,100 };
return ar;//返回数组ar的首地址
}
int main()
{
int* ip = nullptr;
ip = GetArray();
for (int i = 0;i < 10;++i)
{
/*printf("%d", *ip);
ip++;*/
printf("%d", ip[i]);// *(ip+i)
}
return 0;
}
运行结果如下:
4.指针的存放形式
小端存放:
- 高位数存放高地址,低位数存放低地址
- 指针存放某一变量的地址都是存放该变量的首地址
- 低地址做为首地址
sizeof (ch) ; // 1字节
sizeof (cp) ; // 4字节
sizeof (&ch) ; // 4字节
5.类型对指针的作用
(1)解析存储单元的大小
(2)指针变量加1的能力
6.指针的应用
(1)传地址:
(2)指针与单目运算符结合:
#include<stdio.h>
int main()
{
const int n = 5;
int ar[5] = { 12,23,34,45,56 };
// 0 1 2 3 4
int* p = ar;
int x = 0;
int y = 0;
//*与++优先级相同,都是从右向左结合
x = *++p;// p先指向0下标;先++p,p指向1;*p=23
y = *p; // p指向1,所以*p=23
printf("%d %d \n", x, y);//23 23
x = ++*p; // 等价于 ++ar[1]: p指向下标1,*p=23; ++ar[1] -> ar[1]=23+1=24;
// 此处的前置++不改变指针P的指向,只是改变*p的大小
y = *p; // *p=24
printf("%d %d \n", x, y);//24 24
x = *p++; //等价于 x=*p;p++;
// 也等价于 x= *(p++)
// 后置++只有在表达式结束时才运行
y = *p;
printf("%d %d \n", x, y);//24 34
x = (*p)++;//先将p指向的下标2的值给x,后ar[2]++
y = *p;//p指向
printf("%d %d \n", x, y);//34 35
return 0;
}
(3)指针与数组:
int main()
{
const int n = 10;
int ar[n] = { 12,23,34,45,56,67,78,89,90,100 };
for (int i = 0;i < n;++i)
{
printf("%d %d %d \n", ar[i], *(ar + i), i[ar]);
//ar[i] = > *(ar + i);
//i[ar] = > *(i + ar);
}
return 0;
}
(4)指针与函数
当数组名做为形参时就退化为指针
形参为指针一定要对指针判空(代码的健壮性)
void Print_Ar(int br[10],int n);
void Print_Ar(int br[],int n);
void Print_Ar(int* br,int n);
原因:即节省空间又节省时间
#include<stdio.h>
#include<typeinfo>
using namespace std;
void Print_Ar(int br[10])
{
printf("%d \n", sizeof(br));
printf("%s \n", typeid(br).name());//判断br是什么样的类型
// 打印 int * 代表整型指针
int cr[10] = {};
printf("%s \n", typeid(cr).name());
}
int main()
{
const int n=10;
int ar[n] = { 12,23,34,45,56,67,78,89,90,100 };
Print_Ar(ar);
}
运行结果如下:
(5)指针与const
int a = 10 ; // a 是一个变量,变量只有一个 const 修饰
const int a = 10 ; //const 修饰 a 是一个常量
区别于指针:可以有2个 const 修饰
原因:指针有2个值
- 1.存放某个变量的地址,用来操纵指针自身
- 2.解引用时就是某个变量本身,操纵指针指向
(6)指针变量与整型量的加减
- 指针变量与整型量的加减表示移动指针,以指向当前目标前面或后面的若干个位置的地址。指针与整型量i的加减等于指针值(地址)与i*sizeof(目标类型)积的加减,得出新的地址。
int * ip ;
ip + n ; //等价于: ip + sizeof ( int ) * n ;
(7)指针 - 指针
指针和指针可以相减,但是指针与指针不能相加
- 列如:日期 - 日期 =天数:2021.10.3 -2021.10.1 =2天
- 但是:日期 + 日期 无意义
指针相加无意义,但是指针可以加整型
- 等价于:2021.10.5 +5 =2021.10.10
注意 :
- 指针 - 指针 :算头不算尾,相减的值等于元素的个数,并不等于字节的个数
指针不能比较大小,但是可以比较高低
int main()
{
const int n = 5;
int ar[n] = { 12,23,34,45,56 };
int* p = nullptr;
for (p = ar;p < &ar[n];++p)
{
printf("%x=>%d \n", p, *p);
}
return 0;
}
(8)无类型指针
无类型指针可以存放地址,但是通过无类型指针**无法对存放在该地址的数据进行任何修改和识别,**无类型指针无法自加或者自减
- 无类型: void
- 无类型:是一种抽象类型(概念)不具有定义实体的能力,故不能定义变量
- 无类型不能计算大小:sizeof ( void ) ; error;
- 无类型可以定义指针:特点:只要是地址都笑纳
- 但是无类型指针不能解引用;不能自加,不能自减(不清楚无类型指针指向的字节大小)
无类型指针也称为泛型指针,sizeof (无类型指针)= 4 字节
用途:与函数指针相结合,与泛型编程相结合
// size不是元素的个数,是整个数组的大小(字节个数)
void my_memcpy(void* dest, void* src, unsigned int size)
{
assert(dest != nullptr && src != nullptr);
char* cdest = (char*)dest;
char* csrc = (char*)src;
for (int i = 0;i < size;++i)
{
cdest[i] = csrc[i];
}
}
void my_setzero(void* dest, unsigned int size)
//把任意一个空间,从起始地址开始,总共size个字节,把里面每一个字节都设置为0值
{
assert(dest != nullptr && size>0);
char* cp = (char*)dest;
for (int i = 0;i < size;++i)
{
cp[i] = 0;
}
}
struct Student
{
char s_id[20];
char s_name[20];
char s_sex[6];
int s_age;
};
int main()
{
const int n = 10;
int ar[n] = { 12,23,34,45,56,67,78,89,90,100 };
int br[n] = {};
double da[n] = { 1.2,2.3,3.4,4.5,5.6,6.7,7.8,8.9,9.0,1.00 };
double db[n] = {};
struct Student stud1 = { "1901","xsy","man",21 };
struct Student stud2 = {};
my_memcpy(&stud2, &stud2, sizeof(stud1));
my_memcpy(br, ar, sizeof(ar));
my_memcpy(db, da, sizeof(da));
return 0;
}
数组问题总结
1)数组名作为数组首元素的地址可以++ar或ar = ar+1 吗?
- 答:不能,原因有二:
- (1)编译器编译时数组名就会变成数组的首地址,就会变成一个常性地址,即常量,类似于一个常量,不让加减;
- (2)若数组名可以加减,意味着整个数组在内存中移动
2)编译器为什么要把以下标方式操作数组元素要改成指针方式?ar[i] =>*(ar+i);
- 答:主要是对标于汇编指令:基变址寻址方案(ar是数组的首地址,即:基地址,不变;),i变成 0,1,2…相当于变址,依次访问
3)是否可以用i[ar〕方法去访问第i个元素?
- 答:可以。 ar[i] = > *(ar + i); i[ar] = > *(i + ar); 二者等价
4)ar[i]和pl[i]有区别吗?ar是数组名,p是指向数组首元素的指针变量。
- 答:有区别;ar[i]访问数组元素速度快
int main()
{
const int n = 10;
int ar[n]; //一个数组,开辟n个空间,每个空间为整型
int* par[n];//一个数组,开辟n个空间,每个空间是整型指针
int(*sar)[n];//一个指针,指向一个数组,数组开辟n个空间,每个空间是整型
int* (*spar)[n];//一个指针,指向数组的地址,数组开辟n个空间,每个空间是整型指针
int (*pfun)();//一个指针,指向一个函数,函数形参无类型,返回值为整型
int (*pfun[n])();//
return 0;
}