C++编程:复合数据类型—枚举

实际应用中,经常会遇到某个数据对象只能取有限个常量值的情况,比如一周有7天,一副扑克牌有4种花色等等。对于这种情况,C++提供了另一种批量创建符号常量的方式,可以替代const。这就是“枚举”类型enum。

枚举

1. 枚举类型定义

枚举类型的定义和结构体非常像,需要使用enum关键字。

// 定义枚举类型

enum week

{

Mon, Tue, Wed, Thu, Fri, Sat, Sun

};

与结构体不同的是,枚举类型内只有有限个名字,它们都各自代表一个常量,被称为“枚举量”。

需要注意的是:

  • 默认情况下,会将整数值赋给枚举量;
  • 枚举量默认从0开始,每个枚举量依次加1;所以上面week枚举类型中,一周七天枚举量分别对应着0~6的常量值;
  • 可以通过对枚举量赋值,显式地设置每个枚举量的值

2. 使用枚举类型

使用枚举类型也很简单,创建枚举类型的对象后,只能将对应类型的枚举量赋值给它;如果打印它的值,将会得到对应的整数。

week w1 = Mon;

week w2 = Tue;

//week w3 = 2; // 错误,类型不匹配

week w3 = week(3); // int类型强转为week类型后赋值

cout << "w1 = " << w1 << endl;

cout << "w2 = " << w2 << endl;

cout << "w3 = " << w3 << endl;

这里需要注意:

  • 如果直接用一个整型值对枚举类型赋值,将会报错,因为类型不匹配;
  • 可以通过强制类型转换,将一个整型值赋值给枚举对象;
  • 最初的枚举类型只有列出的值是有效的;而现在C++通过强制类型转换,允许扩大枚举类型合法值的范围。不过一般使用枚举类型要避免直接强转赋值。

指针

计算机中的数据都存放在内存中,访问内存的最小单元是“字节”(byte)。所有的数据,就保存在内存中具有连续编号的一串字节里。

 

指针顾名思义,是“指向”另外一种数据类型的复合类型。指针是C/C++中一种特殊的数据类型,它所保存的信息,其实是另外一个数据对象在内存中的“地址”。通过指针可以访问到指向的那个数据对象,所以这是一种间接访问对象的方法。

 

1. 指针的定义

指针的定义语法形式为:

类型 * 指针变量;

这里的类型就是指针所指向的数据类型,后面加上星号“*”,然后跟指针变量的名称。指针在定义的时候可以不做初始化。相比一般的变量声明,看起来指针只是多了一个星号“*”而已。例如:

int* p1; // p1是指向int类型数据的指针

long* p2; // p2是指向long类型数据的指针

cout << "p1在内存中长度为:" << sizeof(p1) << endl;

cout << "p2在内存中长度为:" << sizeof(p2) << endl;

p1、p2就是两个指针,分别指向int类型和long类型的数据对象。

指针的本质,其实就是一个整数表示的内存地址,它本身在内存中所占大小跟系统环境有关,而跟指向的数据类型无关。64位编译环境中,指针统一占8个字节;若是32位系统则占4字节。

2. 指针的用法

 

 

(1)获取对象地址给指针赋值

指针保存的是数据对象的内存地址,所以可以用地址给指针赋值;获取对象地址的方式是使用“取地址操作符”(&)。

int a = 12;

int b = 100;

cout << "a = " << a << endl;

cout << "a的地址为:" << &a << endl;

cout << "b的地址为:" << &b << endl;

int* p = &b; // p是指向b的指针

p = &a; // p指向了a

cout << "p = " << p << endl;

把指针当做一个变量,可以先指向一个对象,再指向另一个不同的对象。

(2)通过指针访问对象

指针指向数据对象后,可以通过指针来访问对象。访问方式是使用“解引用操作符”(*):

p = &a; // p是指向a的指针

cout << "p指向的内存中,存放的值为:" << *p << endl;

*p = 25; // 将p所指向的对象(a),修改为25

cout << "a = " << a << endl;

在这里由于p指向了a,所以*p可以等同于a。

3. 无效指针、空指针和void*指针

(1)无效指针

定义一个指针之后,如果不进行初始化,那么它的内容是不确定的(比如0xcccc)。如果这时把它的内容当成一个地址去访问,就可能访问的是不存在的对象;更可怕的是,如果访问到的是系统核心内存区域,修改其中内容会导致系统崩溃。这样的指针就是“无效指针”,也被叫做“野指针”。

int* p1;

//*p1 = 100; // 危险!指针没有初始化,是无效指针

指针非常灵活非常强大,但野指针非常危险。所以建议使用指针的时候,一定要先初始化,让它指向真实的对象。

(2)空指针

如果先定义了一个指针,但确实还不知道它要指向哪个对象,这时可以把它初始化为“空指针”。空指针不指向任何对象。

int* np = nullptr; // 空指针字面值

np = NULL; // 预处理变量

np = 0; // 0值

int zero = 0;

//np = zero; // 错误,int变量不能赋值给指针

cout << "np = " << np << endl; // 输出0地址

//cout << "*np = " << *np << endl; // 错误,不能访问0地址的内容

空指针有几种定义方式:

  • 使用字面值nullptr,这是C++ 11 引入的方式,推荐使用;
  • 使用预处理变量NULL,这是老版本的方式;
  • 直接使用0值;
  • 另外注意,不能直接用整型变量给指针赋值,即使值为0也不行

所以可以看出,空指针所保存的其实就是0值,一般把它叫做“0地址”;这个地址也是内存中真实存在的,所以也不允许访问。

空指针一般在程序中用来做判断,看一个指针是否指向了数据对象。

(3)void * 指针

一般来说,指针的类型必须和指向的对象类型匹配,否则就会报错。不过有一种指针比较特殊,可以用来存放任意对象的地址,这种指针的类型是void*。

int i = 10;

string s = "hello";

void* vp = &i;

vp = &s;

cout << "vp = " << vp << endl;

cout << "vp的长度为: " << sizeof(vp) << endl;

//cout << "*vp = " << *vp << endl; // 错误,不能通过void *指针访问对象

void* 指针表示只知道“保存了一个地址”,至于这个地址对应的数据对象是什么类型并不清楚。所以不能通过 void* 指针访问对象;一般 void* 指针只用来比较地址、或者作为函数的输入输出。

4. 指向指针的指针

指针本身也是一个数据对象,也有自己的内存地址。所以可以让一个指针保存另一个指针的地址,这就是“指向指针的指针”,有时也叫“二级指针”;形式上可以用连续两个的星号**来表示。类似地,如果是三级指针就是***,表示“指向二级指针的指针”。

 

int i = 1024;

int* pi = &i; // pi是一个指针,指向int类型的数据

int** ppi = π // ppi是一个二级指针,指向一个int* 类型的指针

cout << "pi = " << pi << endl;

cout << "* pi = " << * pi << endl;

cout << "ppi = " << ppi << endl;

cout << "* ppi = " << * ppi << endl;

cout << "** ppi = " << ** ppi << endl;

如果需要访问二级指针所指向的最原始的那个数据,应该做两次解引用操作。

5. 指针和const

指针可以和const修饰符结合,这可以有两种形式:一种是指针指向的是一个常量;另一种是指针本身是一个常量。

(1)指向常量的指针

指针指向的是一个常量,所以只能访问数据,不能通过指针对数据进行修改。不过指针本身是变量,可以指向另外的数据对象。这时应该把const加在类型前。

const int c = 10, c2 = 56;

//int* pc = &c; // 错误,类型不匹配

const int* pc = &c; // 正确,pc是指向常量的指针,类型为const int *

pc = &c2; // pc可以指向另一个常量

int i = 1024;

pc = &i; // pc也可以指向变量

*pc = 1000; // 错误,不能通过pc更改数据对象

这里发现,pc是一个指向常量的指针,但其实把一个变量i的地址赋给它也是可以的;编译器只是不允许通过指针pc去间接更改数据对象。

(2)指针常量(const指针)

指针本身是一个数据对象,所以也可以区分变量和常量。如果指针本身是一个常量,就意味它保存的地址不能更改,也就是它永远指向同一个对象;而数据对象的内容是可以通过指针改变的。这种指针一般叫做“指针常量”。

指针常量在定义的时候,需要在星号*后、标识符前加上const。

int* const cp = &i;

*cp = 2048; // 通过指针修改对象的值

cout << "i = " << i << endl;

//cp = &c; // 错误,不可以更改cp的指向

const int* const ccp = &c; // ccp是一个指向常量的常量指针

这里也可以使用两个const,定义的是“指向常量的常量指针”。也就是说,ccp指向的是常量,值不能改变;而且它本身也是一个常量,指向的对象也不能改变。

6. 指针和数组

 

(1)数组名

用到数组名时,编译器一般都会把它转换成指针,这个指针就指向数组的第一个元素。所以我们也可以用数组名来给指针赋值。

int arr[] = {1,2,3,4,5};

cout << "arr = " << arr << endl;

cout << "&arr[0] = " << &arr[0] << endl;

int* pia = arr; // 可以直接用数组名给指针赋值

cout << "* pia = " << *pia << endl; // 指针指向的数据,就是arr[0]

也正是因为数组名被认为是指针,所以不能直接使用数组名对另一个数组赋值,数组也不允许这样的直接拷贝:

int arr[] = {1,2,3,4,5};

//int arr2[5] = arr; // 错误,数组不能直接拷贝

(2)指针运算

如果对指针pia做加1操作,我们会发现它保存的地址直接加了4,这其实是指向了下一个int类型数据对象:

pia + 1; // pia + 1 指向的是arr[1]

*(pia + 1); // 访问 arr[1]

所谓的“指针运算”,就是直接对一个指针加/减一个整数值,得到的结果仍然是指针。新指针指向的数据元素,跟原指针指向的相比移动了对应个数据单位。

(3)指针和数组下标

我们知道,数组名arr其实就是指针。这就带来了非常有趣的访问方式:

* arr; // arr[0]

*(arr + 1); // arr[1]

这是通过指针来访问数组元素,效果跟使用下标运算符arr[0]、arr[1]是一样的。进而我们也可以发现,遍历元素所谓的“范围for循环”,其实就是让指针不停地向后移动依次访问元素。

(4)指针数组和数组指针

指针和数组这两种类型可以结合在一起,这就是“指针数组”和“数组指针”。

  • 指针数组:一个数组,它的所有元素都是相同类型的指针;
  • 数组指针:一个指针,指向一个数组的指针;

int arr[] = {1,2,3,4,5};

int* pa[5]; // 指针数组,里面有5个元素,每个元素都是一个int指针

int(* ap)[5]; // 数组指针,指向一个int数组,数组包含5个元素

cout << "指针数组pr的大小为:" << sizeof(pa) << endl; // 40

cout << "数组指针ap的大小为:" << sizeof(ap) << endl; // 8

pa[0] = arr; // pa中第一个元素,指向arr的第一个元素

pa[1] = arr + 1; // pa中第二个元素,指向arr的第二个元素

ap = &arr; // ap指向了arr整个数组

cout << "arr =" << arr << endl;

cout << "* arr =" << *arr << endl; // arr解引用,得到arr[0]

cout << "arr + 1 =" << arr + 1 << endl;

cout << "ap =" << ap << endl;

cout << "* ap =" << *ap << endl; // ap解引用,得到的是arr数组

cout << "ap + 1 =" << ap + 1 << endl;

这里可以看到,指向数组arr的指针ap,其实保存的也是arr第一个元素的地址。arr类型是int *,指向的就是arr[0];而ap类型是int (*) [5],指向的是整个arr数组。所以arr + 1,得到的是arr[1]的地址;而ap + 1,就会跨过整个arr数组。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值