1.5数组(Arrays)
1.5.1元素个数
数组定义的格式为:
类型名 数组名[常量表达式];
常量表达式表示数组的元素的个数,并不表示最大下标值。
例如:int a[5];
常量表达式的值只要是整数或整数子集就行,例如:
int a['a'];
数组定义是具有编译确定意义的操作,它分配固定大小的空间,就像变量定义一样的明确,因此元素个数必须是由编译时就能定夺的
常量表达式。下面的数组定义就由问题:
int n=100;
int a[n];//错,数组元素的个数必须是常量
虽然上下文,编译似乎已经知道n的值,但编译动作,因变量性质而完全不同,变量性质就是具有空间占用的可访问实体,编译每次碰到一个变量名称就对应一个
访问空间的操作,因此,int a[n]实际上要在运行时,才能读取变量n的值,才能确定其空间的大小,这与数组定义的编译时确定意义的要求相违背,因而编译出错。
而对于下面的定义,却是允许的,因为编译中,常量虽也占空间,甚至也设计内存访问,但因数据确定,而可以被编译用的常数替代。事实上,常量在编译时经常是优化的目标,
能省略内存空间的访问就应该省略,讲求效率的C++编译器会乐此不疲。
const int n=100;
int a[n];
1.5.2初始化(initialization)
数据的读入一般涉及从其他外部设备中输入的过程,但元素不多而又确定数据值的小数组可以直接在程序中初始化。例如:
int iArray[10]={1,1,2,3,5,8,13,21,34,55};
1.5.3默认值(Default Values)
对于没有初始化的数组,分两种情况:一种是全局数组和静态数组,也就是函数外部定义的,或者加上static修饰的数组定义,其元素总是全被清0,;
另一种是局部数组,就是在函数内部定义的数组,它们的值是不确定的。例如:
#include<iostream>
using namespace std;
int array1[5]={1,2,3};//有初始化 输出:1 2 3 0 0
int array2[5];//无初始化 输出:0 0 0 0 0
int main(){
int array3[5]={2};//有初始化 输出:2 0 0 0 0
int array4[5];//无初始化 输出:124502 845597673 0 0 4198406
//.......//
}
1.5.4 二维数组(2-D Arrays)
……
1.6向量(Vectors)
1.6.1基本操作
vector是向量类型,它是一种对象实体,它可以容纳许多其他类型的相同实体,如若干个整数,所以称其为容器,vector是C++ STL(标准模板类库)的重要一员,使用它时,
只要包含资源vector即可。
vector可以有四种定义方式:
(1)vector<int> a(10);//定义了10个整数元素的向量,但并为给初值,因而其值是不确定的。
(2)vector<int> b(10,1);//定义了10个整数元素的向量,且给出其每个元素的初值为1,这种形式是数组望尘莫及的,数组只能通过循环来成批地赋给相同初值
(3)vector<int> c(b);//用一个现成的向量来创建一个向量
(4)vector<int> d(b.begin(),b.begin()+3);//定义了其值依次为b向量中第0到第2个(共3个)元素的向量。
vector<int>是模板形式,尖括号中为元素类型名,它可以是任何合法的数据类型。
因此,创建向量时,不但可以整体向量复制性赋值,还可以选择其他容器的部分元素来定义向量和赋值。特别的,向量还可以从数组获得初值,例如:
int a[7]={1,2,5,3,7,8,9};
vector<int> va(a,a+7);
上述第四种形式的b.begin()、b.end()是表示向量的起始元素位置和最后一个元素之外的元素位置,向量元素位置也属于一种类型,称为遍历器。遍历器不单表示元素位置,
还可以在容器中前后挪动,每种容器都有对应的遍历器,向量中的遍历器类型为:vector<int>::iterator。因此,如果要输出向量中的所有元素,可以有两种循环控制方式:
for(int i-0;i<a.size();i++)
cout<<a[i]<<" ";
for(vector<int>::iterator it =a.begin();it!=a.end();++it)
cout<<*it<<" ";
a.assign(b.begin(),b.begin()+3); //b向量的0~2元素构成向量赋给a
a.assign(4,2); //使a向量只含0~3元素,且赋为值2
int x=a.back(0;//将a最后一个向量元素值赋给整数变量x
a.clear(0);//a 向量中的元素清空(不再有元素)
if(a.empty(0)
cout<<"empty";//a.empty()经常作为条件,它判断向量空否
int y=a.front(); //将a的第一个向量元素值赋给整数变量y
a.pop_back();//删除a向量的最后一个元素
a.push_back();//在a向量最后插入一个元素,其值为5
a,resize(10);//将向量元素个数调至10个,多则删,少则增补,其值随机
a.resize(10,2);//将向量元素个数调至10个,多则删,少则增补,其值为2
if(a==b) cout<<"equal";//向量的比较操作还有!=,<,<=,>,>=
除此之外,还有元素的插入和删除,保留元素个数,容量观察等操作。
1.6.2二维向量
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
using namespace std;
typedef vector<vector<int>> Mat;//二维向量
Mat input();
void mySort(Mat& a);
void print(const Mat& a);
int main()
{
Mat a=input();
mySort(a);
print(a);
}
Mat input(){
ifstream in("aaa.txt");
Mat a;
for(string s;getline(in,s);)
{
vector<int> b;
istringstream sin(s);//将字符串输入流与s捆绑在一起
for(int ia;sin>>ia;)
b.push_back(ia);
a.push_back(b);
}
return a;
}
void mySort(Mat &a)
{
for(int pass=1;pass<a.size();++pass)
for(int i=0;i<a.size()-pass;++i)
if(a[i+1].size()<a[i].size())
a[i].swap(a[i+1]);
}
void print(const Mat& a)
{
for(int i=0;i<a.size();++i)
for(int j=0;j<a[i].size();++j)
cout<<a[i][j]<<" ";
cout<<endl;
}
1.7指针与引用(Pointers&References)
C++拥有在运行时获得变量或对象地址和通过地址操纵数据的能力,这种能力是通过指针来发挥的。由于其许多高级操作的内部实现都依赖指针,所以指针不但在过程化
程序设计中必不可少,在面向对象的程序设计中也是必不可少的。指针用于数组,用于函数参数,用于内存空间的申请和释放等,指针对于成功进行C++编程至关重要。
也许指针在其他语言中并不是必要的,但在C++中,显得很必要。
指针在提高性能方面,提升C++的产业竞争力上,立下了汗马功劳。指针功能是强大的,但又是最危险的。
1.7.1指针(Pointers)
每个类型都有对应的指针类型,因此指针定义的形式为:
int *ip;
char *cp;
float *fp;
double *dp;
定义中的*可以居左,居右,或居中。由于指针本身也是一种类型,因此,甚至指针本身也有对应的指针类型:
int **iip;//即(int*)* iip;
iip称为二级整形指针变量。
一个*只能修饰一个指针,所以:
int* ip,iq;//则表示ip为指针变量,iq为整形变量
在一个定义语句中定义两个指针变量的方法为:
int *ip,*iq;
其中ip和iq称为指针变量,指针变量的定义,由数据类型后跟星号,再跟指针变量名组成。指针变量在不致引起混淆的情况下也称为指针。
指针可以赋值,也可以在定义指针时初始化,赋值或初始化的值是同类型实体的地址:
int* ip;
int iCount=18;
int* iPtr=&iCount;//初始化
ip=&iCount;//赋值
"&"表示实体的地址,由于字面值不认为是具有空间地址的实体,所以不能进行&操作。
ip=&23;//错
指针指向的实体,可以通过指针的间访操作(dereference)(即在指针变量前加*号的操作)来读写该空间的内容。例如:
int iCount=18;
int* ip=&iCount;
*ip=12;
cout<<*ip<<" "<<iCount<<endl;
显示结果应该是12 12。因此,间访操作对所指向的实体既可以读也可以写。写就意味着实体的改变,意味着也影响了所关联的变量。
由于指针变量本身也为具有空间的实体,因此也具有空间地址,也可以被别的指针(二级指针)所操纵。例如,下面通过二级指针的两次间访,
最终操纵整形实体。
int iCount=18;
int* ip=&iCount;
int** iip=&ip;
其显示结果为18.
指针的0值不是表示指向地址为0的空间,而是表示空指针,即不指向任何空间。而指针只有指向具体的实体,才能使间访操作具有意义:
int* iPtr;
*iPtr=58//错
从另一个角度说,指针忘记赋值,比整形变量忘了赋值危险的多,因为这种错误,不能被编译器所发现,甚至调试的发现能力也很有限,到了运行的时候发现,
可能已经作为发行版本颁发,而来不及挽回损失了。这种不安全性也是C++引入引用的重要意图所在。
1.7.2指针的类型
指针是有类型的。给指针赋值,不但必须是一个地址,而且应该是一个与指针类型相符的变量或常量的地址。
例如有int* ip;//ip为整形指针;float* fp;//则fp为浮点型指针。
指针的类型性体现在间访时,读写所指向的空间实体是以自身的类型来规定其操作的。然而,指针的类型性表明不同类型的指针,其类型是不同的,不能相互赋值。例如
int* ip=&f;//错:float的地址不能赋给int指针
但是从地址值的本质来说,无非是用二进制表示的整数而已。因此从内存空间位置性而言,不同类型的指针又是统一的,都是二进制的地址。所以不能完全隔绝这种不同地址
间的联系。于是,在C中,便有了蛮横的强制转换(cast)操作,专门对付这种需要:
int* ip=(int*)&f;
(int*)的意思是说,该地址的空间不管放着什么数据,都将它看做整形地址看待,甚至该空间放的是函数代码(意义根本不同的二进制代码),它也不管!!!结果导致程序的
极度脆弱,程序员如履薄冰,强制转换成了运行崩溃的梦靥。因此为了换的这种地址转换的灵活性,把所有不小心的误操作的责任统统归咎于程序员了。
那么C++又是怎么看待转换和如何转换的呢?
首先,主要是因为有了指针(和引用)才有了使用转换的需要。
其次,以一种类型的角度去看另一种类型的表示总是怪怪的。
第三,转换的目的拓宽了,单纯地址意义下的重解释转换reinterpret_cast<type>(表达式)是最不讲道理的。
除此之外,还有静态转换static_cast<type>(表达式)、动态转换dynamic_cast<type>(表达式)和常量转型const_cast<type>
1.7.3指针运算
指针值表示一个内存地址,因此它内部表示为整数,这在显示的时候可以看到,指针变量所占空间大小总是等同于整形变量的大小,但它不是整型数,我们重温数据类型的三个要素:
数据表示、范围表示、操作集合。
指针与整形虽有相同的数据表示,相同的范围表示,但他们具有不同的操作。例如:整型变量不能进行间访操作,所以指针与整形不是相同的数据类型。
指针不服从整型数的操作规则。
指针不能赋予一个整型数,要想获得整型数表示的绝对地址,应将整型数重解释转换为对应的指针的类型。例如:
int* ip=1234567;//错,不能进行int到int*的直接转换
int* sp=reinterpret_cast<int*>(1234567);//ok
数组名本身就是表示元素类型的地址。
指针的增减是以该类型的实体大小为单位的。即:
对float指针加6实际增加了24个字节;
对long int指针 加5实际增加了20个字节;
对char 指针减7实际减少了7个字节;
对double 指针减2实际减少了16个字节。
1.7.4指针限定
一个指针可以操作两个实体,一个是指针值(即地址),一个是间访值(即指向的实体)。于是指针的常量性也分为两种:指针常量(constant pointer)和常量指针(pointer to constant)
指针常量是针对指向变量而言的,也就是指针值不能修改的指针。
常量指针是指指向常量的指针的简称。pointer to constant的主体是指针而不是常量。
定义指针常量还是常量指针就看const修饰,若const修饰指针本身,则为指针常量,若修饰指针类型(指向的实体类型),则为常量指针。
const int a=78;
int b=10;
int c=18;
const int* ip=&a;//const修饰指向的实体类型——常量指针
int const* dp=&b;//等价于上一句——常量指针
int* const cp=&b;//const修饰指针*cp——指针常量
const int* const icp=&c;//常量指针常量
*ip=87;//错,常量指针不能修改指向的实体,*ip只能作右值
ip=&c;//Ok:常量指针可以修改指针值。
*cp=81;//ok:指针常量可以修改指向的实体
cp=&b;//错:指针常量不能修改指针值,即使用同一个地址
*icp=33;//错,常量指针常量不能修改指向的实体;
icp=&b;//错,常量指针常量不能修改指针值
int d=*icp;//OK
1.7.5 引用
从逻辑上理解,引用是个别名(alias),当建立引用时,用一个具体类型的实体去初始化别名,之后,别名便于关联其实体的变量(或对象)享受访问的同等待遇。
int someInt =5;
int& rInt=someInt;//初始化
注意,格式上可以写:
int &rInt = someInt;
int & rInt =someInt;
引用定义必须初始化,这是它与指针根本不同的地方。给引用初始化的总是一个内存实体,否则的话,引用就如无根之草,虚无飘渺。初始化的内存实体不是通过一个地址表示,而是通过一个代表该
实体的名称表示,它可以是常量也可以是变量。显然,它也严格要求类型匹配。也就是说,引用的类型与实体的类型应该严格一致的,否则,无法编译通过的。
使用引用就等于一个实体多了一个关联的名字。实体的值因而便任由关联的名称(变量或对象)操作所宰割。因此,修改引用值,就是修改实体值。就是修改对应变量值,而引用的地址操作也就所代表的实体地址
的操作。
一旦引用诞生,就确定了它与一个实体的联系,这种联系是打不破的,直到引用自身的灭亡。从物理上可以理解,引用是一个隐性指针,即引用值是引自所指向的实体。这才是引用(reference)的真意!
引用于实体的关系,看似是直接访问,实为指针的间接访问。这幕后的转换工作,是由编译做的。编译将这个特殊的指针,rInt转换成*rInt操作,因为,引用不能操作自身的地址值,每次访问rInt,实际上
就是在访问所指向的int1实体。
引用实现为指针,但它封锁了作为指针实现的地址操作,又将间访操作暗中转变为直接操作,使得引用从reference的根本实现中抽象出来,对应为alias理解了。引用比之指针的直接效果是。使得间访操作相对来说
更加安全了,也就隔绝了万恶之源。
1.5.1元素个数
数组定义的格式为:
类型名 数组名[常量表达式];
常量表达式表示数组的元素的个数,并不表示最大下标值。
例如:int a[5];
常量表达式的值只要是整数或整数子集就行,例如:
int a['a'];
数组定义是具有编译确定意义的操作,它分配固定大小的空间,就像变量定义一样的明确,因此元素个数必须是由编译时就能定夺的
常量表达式。下面的数组定义就由问题:
int n=100;
int a[n];//错,数组元素的个数必须是常量
虽然上下文,编译似乎已经知道n的值,但编译动作,因变量性质而完全不同,变量性质就是具有空间占用的可访问实体,编译每次碰到一个变量名称就对应一个
访问空间的操作,因此,int a[n]实际上要在运行时,才能读取变量n的值,才能确定其空间的大小,这与数组定义的编译时确定意义的要求相违背,因而编译出错。
而对于下面的定义,却是允许的,因为编译中,常量虽也占空间,甚至也设计内存访问,但因数据确定,而可以被编译用的常数替代。事实上,常量在编译时经常是优化的目标,
能省略内存空间的访问就应该省略,讲求效率的C++编译器会乐此不疲。
const int n=100;
int a[n];
1.5.2初始化(initialization)
数据的读入一般涉及从其他外部设备中输入的过程,但元素不多而又确定数据值的小数组可以直接在程序中初始化。例如:
int iArray[10]={1,1,2,3,5,8,13,21,34,55};
1.5.3默认值(Default Values)
对于没有初始化的数组,分两种情况:一种是全局数组和静态数组,也就是函数外部定义的,或者加上static修饰的数组定义,其元素总是全被清0,;
另一种是局部数组,就是在函数内部定义的数组,它们的值是不确定的。例如:
#include<iostream>
using namespace std;
int array1[5]={1,2,3};//有初始化 输出:1 2 3 0 0
int array2[5];//无初始化 输出:0 0 0 0 0
int main(){
int array3[5]={2};//有初始化 输出:2 0 0 0 0
int array4[5];//无初始化 输出:124502 845597673 0 0 4198406
//.......//
}
1.5.4 二维数组(2-D Arrays)
……
1.6向量(Vectors)
1.6.1基本操作
vector是向量类型,它是一种对象实体,它可以容纳许多其他类型的相同实体,如若干个整数,所以称其为容器,vector是C++ STL(标准模板类库)的重要一员,使用它时,
只要包含资源vector即可。
vector可以有四种定义方式:
(1)vector<int> a(10);//定义了10个整数元素的向量,但并为给初值,因而其值是不确定的。
(2)vector<int> b(10,1);//定义了10个整数元素的向量,且给出其每个元素的初值为1,这种形式是数组望尘莫及的,数组只能通过循环来成批地赋给相同初值
(3)vector<int> c(b);//用一个现成的向量来创建一个向量
(4)vector<int> d(b.begin(),b.begin()+3);//定义了其值依次为b向量中第0到第2个(共3个)元素的向量。
vector<int>是模板形式,尖括号中为元素类型名,它可以是任何合法的数据类型。
因此,创建向量时,不但可以整体向量复制性赋值,还可以选择其他容器的部分元素来定义向量和赋值。特别的,向量还可以从数组获得初值,例如:
int a[7]={1,2,5,3,7,8,9};
vector<int> va(a,a+7);
上述第四种形式的b.begin()、b.end()是表示向量的起始元素位置和最后一个元素之外的元素位置,向量元素位置也属于一种类型,称为遍历器。遍历器不单表示元素位置,
还可以在容器中前后挪动,每种容器都有对应的遍历器,向量中的遍历器类型为:vector<int>::iterator。因此,如果要输出向量中的所有元素,可以有两种循环控制方式:
for(int i-0;i<a.size();i++)
cout<<a[i]<<" ";
for(vector<int>::iterator it =a.begin();it!=a.end();++it)
cout<<*it<<" ";
a.assign(b.begin(),b.begin()+3); //b向量的0~2元素构成向量赋给a
a.assign(4,2); //使a向量只含0~3元素,且赋为值2
int x=a.back(0;//将a最后一个向量元素值赋给整数变量x
a.clear(0);//a 向量中的元素清空(不再有元素)
if(a.empty(0)
cout<<"empty";//a.empty()经常作为条件,它判断向量空否
int y=a.front(); //将a的第一个向量元素值赋给整数变量y
a.pop_back();//删除a向量的最后一个元素
a.push_back();//在a向量最后插入一个元素,其值为5
a,resize(10);//将向量元素个数调至10个,多则删,少则增补,其值随机
a.resize(10,2);//将向量元素个数调至10个,多则删,少则增补,其值为2
if(a==b) cout<<"equal";//向量的比较操作还有!=,<,<=,>,>=
除此之外,还有元素的插入和删除,保留元素个数,容量观察等操作。
1.6.2二维向量
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
using namespace std;
typedef vector<vector<int>> Mat;//二维向量
Mat input();
void mySort(Mat& a);
void print(const Mat& a);
int main()
{
Mat a=input();
mySort(a);
print(a);
}
Mat input(){
ifstream in("aaa.txt");
Mat a;
for(string s;getline(in,s);)
{
vector<int> b;
istringstream sin(s);//将字符串输入流与s捆绑在一起
for(int ia;sin>>ia;)
b.push_back(ia);
a.push_back(b);
}
return a;
}
void mySort(Mat &a)
{
for(int pass=1;pass<a.size();++pass)
for(int i=0;i<a.size()-pass;++i)
if(a[i+1].size()<a[i].size())
a[i].swap(a[i+1]);
}
void print(const Mat& a)
{
for(int i=0;i<a.size();++i)
for(int j=0;j<a[i].size();++j)
cout<<a[i][j]<<" ";
cout<<endl;
}
1.7指针与引用(Pointers&References)
C++拥有在运行时获得变量或对象地址和通过地址操纵数据的能力,这种能力是通过指针来发挥的。由于其许多高级操作的内部实现都依赖指针,所以指针不但在过程化
程序设计中必不可少,在面向对象的程序设计中也是必不可少的。指针用于数组,用于函数参数,用于内存空间的申请和释放等,指针对于成功进行C++编程至关重要。
也许指针在其他语言中并不是必要的,但在C++中,显得很必要。
指针在提高性能方面,提升C++的产业竞争力上,立下了汗马功劳。指针功能是强大的,但又是最危险的。
1.7.1指针(Pointers)
每个类型都有对应的指针类型,因此指针定义的形式为:
int *ip;
char *cp;
float *fp;
double *dp;
定义中的*可以居左,居右,或居中。由于指针本身也是一种类型,因此,甚至指针本身也有对应的指针类型:
int **iip;//即(int*)* iip;
iip称为二级整形指针变量。
一个*只能修饰一个指针,所以:
int* ip,iq;//则表示ip为指针变量,iq为整形变量
在一个定义语句中定义两个指针变量的方法为:
int *ip,*iq;
其中ip和iq称为指针变量,指针变量的定义,由数据类型后跟星号,再跟指针变量名组成。指针变量在不致引起混淆的情况下也称为指针。
指针可以赋值,也可以在定义指针时初始化,赋值或初始化的值是同类型实体的地址:
int* ip;
int iCount=18;
int* iPtr=&iCount;//初始化
ip=&iCount;//赋值
"&"表示实体的地址,由于字面值不认为是具有空间地址的实体,所以不能进行&操作。
ip=&23;//错
指针指向的实体,可以通过指针的间访操作(dereference)(即在指针变量前加*号的操作)来读写该空间的内容。例如:
int iCount=18;
int* ip=&iCount;
*ip=12;
cout<<*ip<<" "<<iCount<<endl;
显示结果应该是12 12。因此,间访操作对所指向的实体既可以读也可以写。写就意味着实体的改变,意味着也影响了所关联的变量。
由于指针变量本身也为具有空间的实体,因此也具有空间地址,也可以被别的指针(二级指针)所操纵。例如,下面通过二级指针的两次间访,
最终操纵整形实体。
int iCount=18;
int* ip=&iCount;
int** iip=&ip;
其显示结果为18.
指针的0值不是表示指向地址为0的空间,而是表示空指针,即不指向任何空间。而指针只有指向具体的实体,才能使间访操作具有意义:
int* iPtr;
*iPtr=58//错
从另一个角度说,指针忘记赋值,比整形变量忘了赋值危险的多,因为这种错误,不能被编译器所发现,甚至调试的发现能力也很有限,到了运行的时候发现,
可能已经作为发行版本颁发,而来不及挽回损失了。这种不安全性也是C++引入引用的重要意图所在。
1.7.2指针的类型
指针是有类型的。给指针赋值,不但必须是一个地址,而且应该是一个与指针类型相符的变量或常量的地址。
例如有int* ip;//ip为整形指针;float* fp;//则fp为浮点型指针。
指针的类型性体现在间访时,读写所指向的空间实体是以自身的类型来规定其操作的。然而,指针的类型性表明不同类型的指针,其类型是不同的,不能相互赋值。例如
int* ip=&f;//错:float的地址不能赋给int指针
但是从地址值的本质来说,无非是用二进制表示的整数而已。因此从内存空间位置性而言,不同类型的指针又是统一的,都是二进制的地址。所以不能完全隔绝这种不同地址
间的联系。于是,在C中,便有了蛮横的强制转换(cast)操作,专门对付这种需要:
int* ip=(int*)&f;
(int*)的意思是说,该地址的空间不管放着什么数据,都将它看做整形地址看待,甚至该空间放的是函数代码(意义根本不同的二进制代码),它也不管!!!结果导致程序的
极度脆弱,程序员如履薄冰,强制转换成了运行崩溃的梦靥。因此为了换的这种地址转换的灵活性,把所有不小心的误操作的责任统统归咎于程序员了。
那么C++又是怎么看待转换和如何转换的呢?
首先,主要是因为有了指针(和引用)才有了使用转换的需要。
其次,以一种类型的角度去看另一种类型的表示总是怪怪的。
第三,转换的目的拓宽了,单纯地址意义下的重解释转换reinterpret_cast<type>(表达式)是最不讲道理的。
除此之外,还有静态转换static_cast<type>(表达式)、动态转换dynamic_cast<type>(表达式)和常量转型const_cast<type>
1.7.3指针运算
指针值表示一个内存地址,因此它内部表示为整数,这在显示的时候可以看到,指针变量所占空间大小总是等同于整形变量的大小,但它不是整型数,我们重温数据类型的三个要素:
数据表示、范围表示、操作集合。
指针与整形虽有相同的数据表示,相同的范围表示,但他们具有不同的操作。例如:整型变量不能进行间访操作,所以指针与整形不是相同的数据类型。
指针不服从整型数的操作规则。
指针不能赋予一个整型数,要想获得整型数表示的绝对地址,应将整型数重解释转换为对应的指针的类型。例如:
int* ip=1234567;//错,不能进行int到int*的直接转换
int* sp=reinterpret_cast<int*>(1234567);//ok
数组名本身就是表示元素类型的地址。
指针的增减是以该类型的实体大小为单位的。即:
对float指针加6实际增加了24个字节;
对long int指针 加5实际增加了20个字节;
对char 指针减7实际减少了7个字节;
对double 指针减2实际减少了16个字节。
1.7.4指针限定
一个指针可以操作两个实体,一个是指针值(即地址),一个是间访值(即指向的实体)。于是指针的常量性也分为两种:指针常量(constant pointer)和常量指针(pointer to constant)
指针常量是针对指向变量而言的,也就是指针值不能修改的指针。
常量指针是指指向常量的指针的简称。pointer to constant的主体是指针而不是常量。
定义指针常量还是常量指针就看const修饰,若const修饰指针本身,则为指针常量,若修饰指针类型(指向的实体类型),则为常量指针。
const int a=78;
int b=10;
int c=18;
const int* ip=&a;//const修饰指向的实体类型——常量指针
int const* dp=&b;//等价于上一句——常量指针
int* const cp=&b;//const修饰指针*cp——指针常量
const int* const icp=&c;//常量指针常量
*ip=87;//错,常量指针不能修改指向的实体,*ip只能作右值
ip=&c;//Ok:常量指针可以修改指针值。
*cp=81;//ok:指针常量可以修改指向的实体
cp=&b;//错:指针常量不能修改指针值,即使用同一个地址
*icp=33;//错,常量指针常量不能修改指向的实体;
icp=&b;//错,常量指针常量不能修改指针值
int d=*icp;//OK
1.7.5 引用
从逻辑上理解,引用是个别名(alias),当建立引用时,用一个具体类型的实体去初始化别名,之后,别名便于关联其实体的变量(或对象)享受访问的同等待遇。
int someInt =5;
int& rInt=someInt;//初始化
注意,格式上可以写:
int &rInt = someInt;
int & rInt =someInt;
引用定义必须初始化,这是它与指针根本不同的地方。给引用初始化的总是一个内存实体,否则的话,引用就如无根之草,虚无飘渺。初始化的内存实体不是通过一个地址表示,而是通过一个代表该
实体的名称表示,它可以是常量也可以是变量。显然,它也严格要求类型匹配。也就是说,引用的类型与实体的类型应该严格一致的,否则,无法编译通过的。
使用引用就等于一个实体多了一个关联的名字。实体的值因而便任由关联的名称(变量或对象)操作所宰割。因此,修改引用值,就是修改实体值。就是修改对应变量值,而引用的地址操作也就所代表的实体地址
的操作。
一旦引用诞生,就确定了它与一个实体的联系,这种联系是打不破的,直到引用自身的灭亡。从物理上可以理解,引用是一个隐性指针,即引用值是引自所指向的实体。这才是引用(reference)的真意!
引用于实体的关系,看似是直接访问,实为指针的间接访问。这幕后的转换工作,是由编译做的。编译将这个特殊的指针,rInt转换成*rInt操作,因为,引用不能操作自身的地址值,每次访问rInt,实际上
就是在访问所指向的int1实体。
引用实现为指针,但它封锁了作为指针实现的地址操作,又将间访操作暗中转变为直接操作,使得引用从reference的根本实现中抽象出来,对应为alias理解了。引用比之指针的直接效果是。使得间访操作相对来说
更加安全了,也就隔绝了万恶之源。