原副标题:C++程式设计:A43EI235E统计数据类别—隐式
前述应用领域中,时常会碰到某一统计数据第一类根本无法取十分有限个自变量值的情形,比如说两周有7天,模样鬼牌有4种花型之类。对此种情形,C++提供更多了另一类大批量建立记号自变量的形式,能代替const。这是“隐式”类别enum。
隐式
1. 隐式类别表述
隐式类别的表述和内部结构体十分像,须要采用enumURL。
// 表述隐式类别
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)。所有的统计数据,就保存在内存中具有连续编号的一串字节里。
指针顾名思义,是“指向”另外一类统计数据类别的A43EI235E类别。指针是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. 指针的用法
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数组。






