C++基础学习系列第一部分——数组和指针

C++ 语言提供了两种类似于 vector 和迭代器类型的低级复合类型——数组和指针。与 vector 类型相似,数组也可以保存某种类型的一组对象;而它们的区别在于,数组的长度是固定的。数组一经创建,就不允许添加新的元素。指针则可以像迭代器一样用于遍历和检查数组中的元素。

数组

    数组是具有相同数据类型的若干变量按序进行存储的变量集合。数组是由类型名、标识符和维数组成的复合数据类型,类型名规定了存放在数组中的元素类型,维数则指定数组中包含的元素个数。数组有一维数组、二维数组及多维数组。

       数组定义和初始化

数组定义中的类型名可以是内置类型、类类型及引用之外的任意复合类型。数组的维数必须用值大于1的常量表达式定义。此常量表达式只能包含整型字面值常量、枚举常量或者用常量表达式初始化的整型const 对象。非 const 变量以及要到运行阶段才知道其值的 const变量都不能用于定义数组的维数。例如:一个一维数组的定义 

数据类型 数组名[常量表达式] int array[10]

数组引用格式为:数组名[下标],特别注意数组元素的下标是从0开始的,在引用的时候不要使下标越界!上面数组中的所有元素值为arrya[0]……arrya[9]共10个元素。

         在定义数组的同时为数组元素提供初始值称为数组的初始化。数组初始化的一般格式为:数据类型

数组名[常量表达式] = {1,2,3,……,n}

如果没有显式提供元素的初值,则数组元素会像普通变量一样初始化:1)在函数体外定义的内置数组,其元素初始值均为02)在函数体内定义的内置数组,其元素无初始值;3不管数组在哪里定义,如果其元素为类类型,则自动调用该类的默认构造函数进行初始化;如果该类没有默认构造函数,则必须为该数组的元素提供显式初始化。显式初始化的数组不需要指定数组的维数值,编译器会根据列出的元素个数来确定数组的长度:

int ia[] = {0, 1, 2}; // an array of dimension 3

如果指定了数组维数,那么初始化列表提供的元素个数不能超过维数值。如果维数大于列出的元素初值个数,则只初始化前面的数组元素;剩下的其他元素,若是内置类型则初始化为0,若是类类型则调用该类的默认构造函数进行初始化。

#include 
   
   
    
    
using namespace std;
int a[5];//全局变量的数组
int main(int argc,char **argv)
{
	int b[2];//函数体内的定义的内置数组
	static int c[2];//函数体内定义的static内置数组
	int d[5]={1,2};//维数大于列出的元素初值个数
	cout<<"数组a的元素为:"<
    
    
   
   

                             

运行结果如下图示:

    对于二维数组,其定义格式为:

数据类型 数组名[常量表达式1][常量表达式2] int array[2][3]

引用格式为:数组名[下标1][下标2]

以上面为例,二维数组可以理解为arrya是由两个元素组成,每个元素array[i]由包含三个元素的一维数组组成的。二维数组初始化跟一维数组一样,这里就不再举例。

          一个数组不能用另外一个数组初始化,也不能将一个数组赋值给另一个数组,这些操作都是非法的:

intia[] = {0, 1, 2}; // ok: array of ints

intia2[](ia); // error: cannot initialize one array with another

数组操作

         在强调一下,数组元素是通过下标来访问的,数组下标是从0开始计数的,对于包含10个元素的数组,其下标值是从09。数组下标的正确类型为size_t下面,将以一个例子来阐述一下关于数组的操作。

#include 
    
    
     
     
using namespace std;
int main(int argc,char **argv)
{
	const size_t array_szie = 10;
	int array_temp[10]={1,2,3,4,5};
	int array_a[array_szie]; //local array,element uninitialzed
	int array_b[array_szie]; //local array,element uninitialzed
	for (size_t i=0;i!=array_szie;++i)//for循环把下标当作值赋值给数组元素
	{
		array_a[i]=i;
	}
	cout<<"数组array_a的元素为:";
	for (size_t i=0;i!=array_szie;++i)//for循环把下标当作值赋值给数组元素
	{
		array_b[i]=array_temp[i];
	}
	for (size_t i=0;i
     
     
    
    

 运行结果如下图:

指针

         指针是一个特殊的变量,它里面存储的数值被解释为内在里的一个地址。要搞清楚一个指针,需要搞清楚指针四个方面的内容:指针的类型;指针所指向的类型;指针的值(也叫做指针所指向的内存区);指针本身所占据的内存区。

指针变量

         指针也是一种变量,普通的变量包含的是实际的数据,而指针变量包含的是内存中的一块地址,这块地址指向某个变量或者函数,指针就是地址。它告诉程序在内存的哪块区域可以找到数据。关于指针的四个方面的内容,下面将根据例子进行说明。

指针的类型从语法上讲,指针的类型就是把指针声明语句中的指针名字去掉所剩下的部分。指针指向的是一块内存区域,指针所指向的类型取决于这块内存在编译时是什么类型,比如一个int*类型指针所指向的类型是int。在理解指针声明的时候请从右往左读。在声明指针的时候有两种风格: int* pint *p。对于第一种把p定义为一个指向int型对象的指针,这种风格容易把int*理解成一种数据类型,认为在同一声明语句中定义的其他变量也是指向 string 类型对象的指针。然而,语句string* ps1, ps2,实际上只把 ps1 定义为指针,而 ps2 并非指针,只是一个普通的 string对象而已。如果需要在一个声明语句中定义两个指针,必须在每个变量标识符前再加符号 * 声明;第二种风格把p定义为一个指向int型对象的指针变量,这种风格更强调声明的对象是个指针,如string *ps1, *ps2;指针包含两个类型:指针类型和指针所指向的类型,声明时是声明指针类型,使用时是使用指针所指向的类型。下面看一些具体的例子:

int *p;//指针类型为:int* 指针指向的类型为:int

char *p;//指针类型为:char* 指针指向的类型为:char

int **p;//指针类型为:int**指针指向的类型为:int*

int* p[3];//优先级[]*高,p是数组,再加上int*,可以称它为指针数组,数组的每一个元素的值都为指针(地址)

int (*p)[3];//*p可以看作是普通变量,就回到第三种情况(intp[3]),但是这里p是指针变量,它指向的是一个包含3个整型数值的数组。可以称它为数组指针,数组中每一个元素的值为普通整型值。

int (*p)(int);//p是函数指针,指向的是返回值为int并且带一个int参数的函数。

指针的值:指针的值或者叫指针所指向的内存区或地址,是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在上例中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。一个有效的指针必然是以下三种状态之一:保存一个特定对象的地址;指向某个对象后面的另一对象;或者是0值。若指针保存0 值,表明它不指向任何对象。未初始化的指针是无效的,直到给该指针赋值后,才可使用它。如果使用未初始化的指针,会将指针中存放的不确定值视为地址,然后操纵该内存地址中存放的位内容。使用未初始化的指针相当于操纵这个不确定地址中存储的基础数据。因此,在对未初始化的指针进行解引用时,通常会导致程序崩溃。C++ 语言无法检测指针是否未被初始化,也无法区分有效地址和由指针分配到的存储空间中存放的二进制位形成的地址。建议程序员在使用之前初始化所有的变量,尤其是指针。如果可能的话,除非所指向的对象已经存在,否则不要先定义指针,这样可避免定义一个未初始化的指针。如果必须分开定义指针和其所指向的对象,则将指针初始化为 0。因为编译器可检测出 0 值的指针,程序可判断该指针并未指向一个对象。以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?

         指针本身所占有的内存:指针本身所占有的内存区是指针本身占内存的大小,这个你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据了4个字节的长度。

指针初始化和赋值操作

         对指针进行初始化或赋值只能使用以下四种类型的值:1.在编译时可获得 0值的整型 const对象或字面值常量 0;2. 类型匹配的对象的地址;3. 另一对象末的下一地址;4. 同类型的另一个有效指针。

int ival;

int zero = 0;

const int c_ival =0;

int *pi = ival;// error: pi initialized fromint value of ival

pi = zero;// error: pi assigned intvalue of zero

pi = c_ival;// ok: c_ival is a const withcompile-time value of 0

pi = 0;// ok: directly initialize toliteral constant0

除了使用数值0 或在编译时值为 0 的 const 量外,还可以使用C++ 语言从 C 语言中继承下来的预处理器变量 NULL(第 2.9.2 节),该变量在 cstdlib头文件中定义,其值为 0。由于指针的类型用于确定指针所指对象的类型,因此初始化或赋值时必须保证类型匹配。

double dval;

double *pd =&dval;// ok: initializer is addressof a double

double *pd2 = pd;// ok: initializer is a pointerto double

int *pi = pd;// error: types of pi and pddiffer

pi = &dval;// error: attempt to assignaddress of a double to int *

指针用于间接访问对象,并基于指针的类型提供可执行的操作。

         void*指针C++提供了一种特殊的指针类型 void*,它可以保存任何类型对象的地址。

double obj = 3.14;

double *pd = &obj;// ok: void* can hold the addressvalue of any data pointer type

void *pv = &obj;// obj can be an object of anytype

pv = pd;  // pd can be a pointer to any type

void* 表明该指针与一地址值相关,但不清楚存储在此地址上的对象的类型。void* 指针只支持几种有限的操作:与另一个指针进行比较;向函数传递void* 指针或从函数返回void* 指针;给另一个 void* 指针赋值。不允许使用void* 指针操纵它所指向的对象。

指针的操作

         指针提供间接操纵其所指对象的功能。对指针进行解引用可访问它所指的对象,* 操作符(解引用操作符)将获取指针所指的对象:

string s("hello world");

string *sp = &s;// sp holds the address of s

cout <<*sp;// prints hello world

对 sp 进行解引用将获得 s 的值。解引用操作符返回指定对象的左值,利用这个功能可修改指针所指对象的值:

*sp = "goodbye";// contents of s now changed

因为 sp指向 s,所以给 *sp 赋值也就修改了 s 的值。也可以修改指针 sp 本身的值,使 sp 指向另外一个新对象:

string s2 ="some value";

sp = &s2;// sp now points to s2

给指针直接赋值即可修改指针的值——不需要对指针进行解引用。

    给指针赋值和通过指针进行赋值这两种操作的差别确实让人费解。谨记区分的重要方法是:如果对左操作数进行解引用,则修改的是指针所指对象的值;如果没有使用解引用操作,则修改的是指针本身的值。

指向指针的指针

         指针本身也是可用指针指向的内存对象。指针占用内存空间存放其值,因此指针的存储地址可存放在指针中。下面程序段:

int ival = 1024;

int *pi =&ival;// pi points to an int

int **ppi =&pi; // ppi points to a pointer toint

定义了指向指针的指针。C++ 使用 ** 操作符指派一个指针指向另一指针。这些对象可表示为:ppi->pi->ival。对 ppi 进行解引用照常获得ppi 所指的对象,在本例中,所获得的对象是指向 int 型变量的指针 pi:

int *pi2 = *ppi;// ppi points to a pointer

为了真正地访问到 ival 对象,必须对 ppi 进行两次解引用。

指针与引用比较

         虽然使用引用和指针都可间接访问另一个值,但它们之间有很重要区别:1)指针是一个地址值,而引用是一个实体的别名。指针,是一种数据类型,它的值是一个地址,当声明一个指针,编译器会为这个指针变量分配内存空间;而引用不是数据类型,引用本身不会占内存空间,编译器也不会为引用分配空间。2)指针可以指向不同的地址空间,但是引用一旦定义,只能指向那个固定的实体。3)指针可以在定义的时候初始化,也可以定义为NULL值,也可以在定义的时候不初始化,过后再指定它的值,而引用必须在定义的时候初始化,而且必须用某个实体对其进行初始化,一旦定义完成,其值不可更改。4)在传参的时候,使用指针传参,编译器需要给指针另行分配存储单元,存储一个该指针的副本,在函数中对这个副本进行操作;使用引用传参,编译器就不需要分配存储空间和保存副本了,函数将直接对实参进行操作。

#include 
     
     
      
      
using namespace std;

void point_switch(int *a,int *b)//址传递
{
	int temp;
	temp=*a;
	*a=*b;
	*b=temp;
}

void reference_switch(int &a,int &b)//引用传递
{
	int temp;
	temp=a;
	a=b;
	b=temp;
}

int main(int argc,char **argv)
{	
	int a=3,b=4;
//	int *pa=&a,*pb=&b;
	cout<<"原始数据为:"<
      
      
       
        具体运行结果如下图:
       
       

 

         数组和指针这一块理解起来不是那么容易,下一期的时候还会继续对数组和指针进一步进行行了解。希望大家多多关注支持我!文中有什么不妥的地方,望大家多多指正,谢谢!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值