专业编程基础技术教程

网站首页 > 基础教程 正文

C++|指针,理解指针从为什么需要指针这种数据类型开始

ccvgpt 2024-10-09 09:04:32 基础教程 6 ℃

世间万物可以数字化,进一步,可以比特化。

用足够多的比特位可以用来表示不同类别的事物。

C++|指针,理解指针从为什么需要指针这种数据类型开始

分类是理解和认识世界的一种很重要的方式。

数据类型是所有编程语言所使用的一种语法机制。

不同类型确定不同的编码解码方式、不同的取值范围、不同的运算操作。

比特化方便硬件实现、其对应的布尔运算、逻辑运算、电路实现简单、可靠。但一串串的比特位太过抽象晦涩,虽然在某一方面有优势,但理解起来困难,解决的办法就是编码,用编码来表示,用解码来理解,通过编码解码实现交互。正像摩尔斯电码、盲文一样。

现象世界的数字、字符、图像、音频、视频需要编码:

I 以补码方式存储整数;

II 通过ASCII编码方式存储字符;

III 以real-4或real-8方式存储浮点数(实数或小数);

位图用像素点、音频通过声音波形来数字化,而视频是图像也音频的合成。

开关晶体管是比特位的硬件实现,由开关晶体管组成的逻辑电路不仅可以执行布尔运算,还可以通过循环电路实现记忆功能。

代码和数据在存储到内存中,由控制器通过取指令、解码指令(操作码+地址码),产生控制信号、从内存存取数据,由运算器执行布尔操作。

内存单元以一种线性关系排列,8个位做为一个字节,作为基本的存储单位,对应一个内存地址,不同的数据类型根据其取值范围的不同,需要不同长度的字节数,如double类型就使用8个字节(对应32个比特,32个晶体管),虽然包含有8个内存单元地址,但只用首地址来表示这一个数据。这个首地址我们一般不直接使用,而是用一个名字来表示,如果规定这个空间不被修改,只读,只被访问,这样的名字就称为常量,如果规定这个空间可以被修改,这样的名字称为变量。变量\常量与内存地址的对应关系由编译器与操作系统完成,一个变量\常量名就代表一块内存空间,在表达式中可以出现在表达式的左端或右端,出现在右端时表示引用内存空间解码后的数据值,出现在左端表示初始化或修改这块内存空间的值(常量初始化后即不能被修改)。

一个变量对应两个值:

I 地址值,由变量名显式表示,用于随机寻址存取;

II 数据值,即比特位按编码方式的解码;

double varName;

8个字节,首地址用varName表示,其初始化、赋值、存取用real-4或real-8编码方案。

varName出现在赋值表达式的左边时,用其地址值,表示对这一块内存空间的初始化或修改;

varName出现在赋值表达式的右边时,用其数据值,表示对这一块内存空间的比特码解码为double数据;

使用&data可以取得其地址,*(&varName)可以引用data地址对应的数据值,*(&varName)==varName。

varName隐藏了地址的复杂性和抽象性,但同时也丧失的地址本身的线性关系。

在编程中,有以下一些情况需要(或更有优势)显式地使用内存单元地址。

1 相同数据类型的数据顺序存储时。

如需要60个double类型的数据,使用60个变量名吗?显然不合适,如果利用地址的线性关系,用首地址来表示容纳这60个变量的空间,每一个变量都可以考虑通过地址偏移去存取;

2 函数参数的传值,如果除了传数据值以外,还可以传其地址值,可以增加函数的功能,能修改函数的外部数据,且传地址值不再需要开辟额外的内存空间并赋值,当数据量较大时,有更高的效率。

3 另一种存储方式(链式存储)的实现,顺序存储有其优势,但也有不足的地方,当数据量较大时,很可能找不到这样一块内存空间而分配内存失败,且插入、删除元素时,操作效率较低。

存取的一个原则就是“存得进去,取得出来。”现实世界中东西的存取与内存的存取有异曲同工之处。如果你有60个东西要存储,但找不到这么大的空间,你可以分空间存储,依次在每一个位置放置一个下一个位置的地址即可顺藤摸瓜找到全部。数据在内存中的链式存储也是一样,单个数据元素再加上一个地址,这个地址依次指向下一个位置,就可实现“存得进去、取得出来。”

4 动态存储的实现,静态数据需要在编译前(即编码阶段)确定数据元素的数量,而不是用一个变量来灵活实现实际需要,显然这样比较笨拙。程序可以向操作系统申请数量不等的内存空间,并返回一个地址。

5 效率

一个数组如果存放6000个名字,如果要等长实现这些名字,则有空间的浪费,如果存储6000个地址,用这些地址去访问对应的名字,则不等长也可以实现。

怎样显示地使用一个内存单元的地址?

上面提到,一个变量对应两个值:

I 地址值,由变量名显示表示;

II 数据值,即比特位按编码方式的解码;

如果这个数据值解析为地址,那这个变量就是一个存储地址的变量了,这样的变量在C++中就叫指针变量。

指针变量对应的两个址都是地址值,一个是其本身的地址,一个是其值被解析为一个内存地址(虽然是一个整数,但不能显式地用一个整数去赋值),这个地址值指向另外一个内存单元空间。指向的内存单元空间可以按数据类型解析。(如果指向的数据类型还是一个地址,则这个指针变量称为双重指针)

有了指针,以上6种情况就有更好的方案:

1 数组实现线性顺序存储,数组名就是首地址,使用[]就可以实现地址偏移;

#include <iostream>
using namespace std;
int main ()
{
 int flag = 999;
 int arr[ 10 ];
 for ( int i = 0; i < 10; i++ )
 {
 arr[ i ] = i + 100;
 }
 //数组名解释为一个常量指针,指向首元素地址
 cout<<arr[3]<<" " <<*(arr+3)<<endl;//数组名解释为一个常量指针
 cout<<*(int*)(&arr+1)<<endl;//输出999
 system("pause");
 return 0;
}

*(int*)(&arr+1)为什么与flag的值相等?是因为flag的值存储在整个数组的后面,也就是arr[9]内存块的下一个单元是flag的内存位置。在&arr的上下文中,arr不是被理解为首元素地址,而是一个数组指针变量(int*)[],变量用&取地址,其偏移的操作是sizeof(arr[0])*10。(int*)表示将这个地址转换为int指针,最外面的*表示这个指针的解引用。

2 用指针作为函数参数;(C++还可以用引用为为函数参数实现传址);

#include <iostream>
using namespace std;
void swap(int* a,int* b)
{
 int t;
 t = *a;
 *a = *b;
 *b = t;
}
int main ()
{
 int a=3,b=7;
 swap(&a,&b);
 cout<<a<<" "<<b<<endl;
 system("pause");
 return 0;
}

3 链表实现

#include <iostream>
using namespace std;
struct node //定义结点结构类型
{
 char data; //用于存放字符数据
 node *next; //用于指向下一个结点(后继结点)
};
 // …………创建…………
node * Create()
{
 node *head=NULL; //表头指针,一开始没有任何结点,所以为NULL
 node *pEnd=head; //表尾指针,一开始没有任何结点,所以指向表头
 node *pS; //创建新结点时使用的指针
 char temp ; //用于存放从键盘输入的字符
 cout<<"请输入字符串,以#结尾:" <<endl;
 do //循环至少运行一次
 {
 cin>>temp;
 if (temp!='#') //如果输入的字符不是#,则建立新结点
 {
 pS=new node; //创建新结点
 pS->data=temp; //新结点的数据为temp
 pS->next=NULL; //新结点将成为表尾,所以next为NULL
 if (head==NULL) //如果链表还没有任何结点存在
 {
 head=pS; //则表头指针指向这个新结点
 }
 else //否则
 {
 pEnd->next=pS; //把这个新结点连接在表尾
 }
 pEnd=pS; //这个新结点成为了新的表尾
 }
 }while (temp!='#'); //一旦输入了#,则跳出循环
 return head; //返回表头指针
}
 //…………遍历…………
void Showlist(node *head)
{
 node *pRead=head; //访问指针一开始指向表头
 cout<<"链表中的数据为:" <<endl;
 while (pRead!=NULL) //当访问指针存在时(即没有达到表尾之后)
 {
 cout<<pRead->data; //输出当前访问结点的数据
 pRead=pRead->next; //访问指针向后移动(指针偏移)
 }
 cout<<endl;
}
int main() 
{
 node *head=NULL;
 head=Create();
 Showlist(head);
 cin.get();
 return 0;
}

4 动态存储、动态数组的实现

#include <iostream>
using namespace std;
int main ()
{
 int n;
 cout<<"请输入需要输入的数组元素个数:";
 cin>>n;
 int* arrp = new int[n];
 for(int i=0;i<n;++i)
 {
 arrp[i] = i+1;
 cout<<arrp[i]<<" ";
 }
 system("pause");
 return 0;
}

5 指针数组存储批量不等长字符串

char *s[10]={"12345","3216","213","abcde","cba", "6bca","12a","b23c","3c124","4d5"};

-End-

最近发表
标签列表