l 文字常量
char 可以表示小整数(0~255)或者单个字符。一个机器字节。在32位机中,int与long通常为一个机器字,short为半字。float、double、long double分别为一个字、两个字,三个或四个字。
当一个数值,例如1,出现在程序中时,它被称为文字常量(literal constant):称为“文字”是因为我们只能以它的值的形式指代它,称之为“常量”是因为它的值不能被改变。文字常量是不可寻址的(nonaddressable),就是说我们不能访问它的地址。
20 // 十进制
024 // 前面加个0,八进制
0x14 // 加0x十六进制
缺省情况下,整型文字常量被当作是一个int的有符号值(u只能修饰整型的文字常量):
128u 1024ul 1L 8LU
浮点型文字常量可以写成科学计数法形式或普通的十进制形式,缺省情况下被认为是double型。单精度文字常量加f、F,扩展精度加L、l。注意f、F、L、l只能加在十进制形式中:
3.14159F 0.1f 12.345L 0.0 3e1 1.0E-3 2 . 1.0L 3.14e1L
字符文字前面可以加上“L”,如 L’a’,称之为宽字符文字,类型为wchar_t,主要用来支持某些语言的字符组成,如汉语、日语,这些语言中的某些字符不能用单字节表示。同样也有宽字符串,如:L “a wide string literal”。
字符串文字常量由零个或多个用双引号括起来的字符组成。在一行的最后面加上一个反斜杠,表明字符串文字在下一行继续:
#include<iostream.h>
main(){
char c[]="puma are large cat-like animals which are found in America
";
cout<<c<<endl;
}
如果两个字符串或宽字符串在程序中相邻,c++就会把它们连接在一起,并在最后加上一个空字符,如:”two”,”some”输出是:”twosome”:
#include<iostream.h>
main(){
char c[]="hello " "world!";
cout<<c<<endl;}
但不建议使用:“two” L “some”
结果是未定义,就是说没为这两种不同类型的连接定义标准行为。用未定义行为的程序被称作是不可移植的,即不能保证相同的程序在不同的编译器、或当前编译器的未来版本下编译。所以,建议不要使用未定义的程序特性。
l 变量
#include<iostream.h>
int pow(int val,int exp){
int value=1;
for(int i=1;i<=exp;i++)
value*=val;
return value;}
main(){
cout<<pow(10,5);
}
变量和文字常量都有存储区,并且有相关的类型。区别在于变量是可寻址的(addressable)。对于每一个变量,都有两个值与之相联:
1. 数据值,存储在某个内存地址中,有时候称为右值(rvalue),可以认为是被读取的值(read value)。文字常量和变量都可以用作右值。
2. 地址值,存储数据值的那块内存地址。有时称为变量的左值(lvalue),可以认为是位置值(location value)。
变量的定义会引起相关内存的分配。因为一个对象只能有一个位置,所以程序中每个对象只能被定义一次。如果在一个文件中定义的对象需要在另一个文件中被访问,就可能出现问题。我们可以通过声明(declaring)该变量来做到这一点:extern string filename;对象声明作用是使程序知道该对象的类型、名字。不引起内存分配
变量名,即变量的标识符(identifier),可以由字母,下划线,数字组成,必须以字母、下划线开始,区分大小写。
一个简单的定义制定了变量的类型和标识符,它并不提供初始值。如果一个变量是在全局域(global scope)内定义的,那么系统会初始为0。如果是在局部域(local scope)内定义的,或通过new分配的,则不提供初始值0。这些对象是未初始化的(undefined),与之相联的内存区域含有一个随机的位串。C++支持两种形式的初始化:
Int val=1024;
String project=”Fantasia 2000”;
Int val(1024);
String project(”Fantasia 2000”);
在对象的定义中,当对象的标识符在定义中出现后,对象名马上就是可见的,因此用对象初始化自己是合法的,但不明智:int a=a。另外每种内置数据类型都支持一种特殊的构造函数语法,可将对象初始化为0:
Int ival=int();
Double dval=double();
//int()自动应用在ivec包含的10个元素上:
Vector <int> ivec(10);
Int ival(int());
//cin不允许同时定义:
Cin>>int input_file;
对象可以用任意复杂的表达式来初始化,包括函数的返回值。
l 指针
每个指针都有一个相关的类型,不同数据类型的指针之间的区别不是在指针的表示上,也不在于指针所持有的值(地址)上——对所有类型的指针这两方面是一样的。不同在于指针所指的对象类型上。指针的类型可以指示编译器怎么解释特定地址上的内存的内容,以及该内存区域应该跨越多少内存单元。不是说pi不能在物力上与dval持有一样的地址,能够,只是彼此对那块内存的存储布局和内容的解释完全不同。而空类型指针可以被任何数据指针类型的地址值赋值(函数以外):
Int a=0,*b=&a;
Double c=0.1,*d=&c;
Void *p=b;
//ok
P=d;
//ok
指针可以让它的地址值增加或减少一个整数值。这类指针操作,称为指针的算术运算(pointer arithmetic)。是离散的十进制的加法,具体加多少取决于指针的数据类型所占用的内存大小。典型用法是遍历一个数组。
l 字符串类型
C++提供两种字符串的表示方法:c风格的字符串和标准c++引入的string类类型。建议使用string类。
1. C风格字符串
字符串存储在一个字符数组中,一般通过一个char *类型指针操纵它。然而,系统内部实际上是存储在一个字符串数组中,然后,st指向数组的第一个元素:
Char *st=”hello world”;
常用指针的算术运算来遍历c风格的字符串,每次指针增加1,直到空字符为止:
While(*st++){…}
当包含:#include<cstring.h>
//返回长度
Int strlen(const char *);
//比较两字符串是否相等
Int strcmp(const char*,const char *);
//以下两个的使用方式不清楚
//第二个字符拷贝至第一个
Char *strcpy(char *,const char *);
//第二个字符接至第一个后面
Char *strcat(char*,const char *);
C风格字符串的长度可以是0,有两种方式:
Char *pc1=0;
Char *pc=””;
一个测试程序:
#include<iostream.h>
// without ".h",eagerly learn to know why
#include<cstring>
main(){
// temp setting avoids chaning the value of p;
//and p can`t be constant,for it will be resetted by function strcat();
char *p="Hello world!",*temp=p;
const char *q="Nice to see you!";
cout<<p<<q<<endl;
cout<<*p<<","<<*(q+1)<<endl;
p=temp;
cout<<"p is made by:\n";
for(int i=0;i<strlen(p);i++)
cout<<*temp++<<endl;
}
2. 字符串类型
C++标准库对此进行支持。
使用string类,必须包含头文件:
#include<string>
String st(“Hello world”);
//返回长度(不包含空字符)
St.size();
String st2;
//检查是否是空
St2.empty()=true or false;
//复制两个字符串
String st3(st);
St2=st3;//simple
//比较是否相等
If(st2==st3){….}
//两个字符串连接成第三个字符串
String s1(“hello,”);
String s2(“world!”);
String s3=s1+s2;
//或者,直接加到s1
S1+=s2;
//可以把一个c风格的字符串转换成string类对象
Const char *pc=”a try”;
String s1=pc;//ok
//但,反向的转换不能自动执行
Char *str=s1;//error
为实现这种转换必须显示调用名为c_str()
//几乎正确的
char *str=s1.c_str();
但为了防止字符数组被程序直接处理,c-str()返回的一个指向常量数组的指针:const char*,str被定义为非常量指针,所以违例。正确是:
Const char *str=s1.c_str();//ok
String 类型支持通过下标操作符号访问单个字符。
一个例子:
l const 限定修饰符
for( int index=0;index<512;index++)
第一个问题是可读性,即512是什么意思?第二个问题是可维护性,如多处修改会陷入麻烦。
Const int bufsize=512;
For(int index=0;index<bufsize;index++)
Const类型限定符提供了一个解决方案,把一个对象(变量)转换成一个常量(constant)。在程序中任何改变这个值的企图都将导致编译错误,因此,被称为只读的。
而且常量定义后必须初始化,因为不能修改。
1. 指向常量的指针(const double *p)
//Const指针:
const double minWage=9.60;
//ok?error?
Double *ptr=&minWage;
//ok?
*ptr+=1.0;
任何“试图将一个非const对象的指针指向一个常量对象”动作都将引起编译失误。这并不意味着我们不能间接访问一个const对象,只意味着我们必须声明一个指向常量的指针来作这事情:
Const double *cptr;
Cptr是一个指向double类型的const对象指针。此中微妙的是cptr本身是个变量,可以重新赋值,使其指向不同对象,但不能修改cptr所指向对象的值。
Const double *pc=0;
Const double minWage=9.60;
//ok,不能通过pc修改minWage
Pc=&minWage;
Double dval=3.14;
//ok:不能通过pc修改dval
Pc=&dval;//ok
Dval=3.14;
*pc=3.14;//error
Const对象的地址只能赋值给const对象指针,例如pc。但是指向const对象的 指针可以被赋予一个非const对象的地址:
Pc=&ival;
虽然ival不是常量,但试图通过pc区修改它的值,将会导致错误,因为运行程序的任意一点上,编译器不能确定指针所指的实际对象。
在实际程序中,指向const的指针常用作函数的形参。
2. 常量指针(double *const p)
可以定义一个const指针指向一个const或一个非const对象。例如:
Int errNum=0;
Int *const curErr=&errNum;
//curErr是一个指向int类型对象的const指针(从右往左读)。这意味着不能赋予curErr其他的地址值,但可以修改curErr指向的值。
//error
const int i=1;
int *const p=&i;//i是const int,而不是int
任何试图给const指针赋值的行为将发生错误:curErr=&myErrNum;//error,unacceptable
3. const对象的const指针
将之前两种形式综合起来:
Const double pi=3.14159;
Const double *const pi_ptr=π
这样,pi_ptr是指向被定义为const的double类型对象的const指针。其指向的对象的值和它的地址值都不能修改。而且一样必须初始化,之后不能改变该指针的值。
l 引用类型
引用(reference)有时候称为别名(alias),它可以用作对象的另一个名字。通过引用我们可以间接地操纵对象,使用类似指针方式,但没有指针的语法。实际的程序中,引用主要用作函数的形式参数——将类对象传递给一个函数。引用必须初始化。
const引用可以用不同类型的对象初始化,也可以是不可寻址的值,如文字常量:
double dval=3.14159;
const int &ir=1024;
const int &ir2=dval;
const double &dr=dval+1.0;
同样的初始化对非const的引用是不合法的,将导致编译错误:
引用在内部存放的是一个对象的地址,它是该对象的别名。对于不可寻址的值,如文字常量,以及不同类型的对象,编译器为了实现引用,必须生成一个临时对象,引用实际上指向该对象,但用户不能访问它:
double dval=1024;
const int &ri=dval;
//转变成:
int temp=dval;
const int &ri=temp;
这样改变的是temp的值,而不是dval。所以const引用指向需要临时变量的对象是不允许。
const int ival=1024;
//如果希望引用ival的地址,则:
const int *const & p=&ival;
引用和指针有两个主要的区别:引用必须总是指向一个对象。如果用一个引用给另一个引用赋值,那么改变的是被引用的对象,而不是引用本身。
l 布尔类型
文字false与true能自动转换成整数值0和1一样,如果有必要,算术值和指针值也能隐式地被转换成布尔类型地值。0或空指针被转换成false,所有其他值被转换成true。
l 枚举类型
枚举(enumeration)提供了一种替代方法,不但定义了整数常量,而且还把它们组成一个集合。例如:
enum open_modes{input=1,output,append};
open_file(“phoenix and the Crane”,append);
如果试图向open_file()传递一个input、output、append之外的值,就会产生编译错误。而且传递一个相等的整数值,编译器也依然会出错。
可以声明枚举类型对象:
open_modes om=input;
//....
om=append;
但不能打印一个由枚举成员的实际枚举名,输出的是对应的整数值。一种解决的方案是设置一个由枚举成员的值索引的字符串数组:
open_modes_table[input];
二是不能通过枚举成员进行迭代:
for(open_modes om=input;input!=append;input++)
l 数组类型
数组的维数值必须是常量表达式——即,必须能够在编译的时候计算出它的值。所以非const的变量是不能用来指定数组的维数。
//根据元素确定数组维数
int ia[]={0,1,2};
//如果指定了维数,那么初始化列表提供的元素的个数不能超过这个值。否则出错。如果指定的维数大于给出的元素的个数,那么没有被显示初始化的元素将被置为0。
一个数组不能被另一个数组初始化,也不能被赋值给另一个数组,而且不允许声明一个引用数组(由引用组成的数组),数组的复制主要通过逐个元素的复制。
任意结果为整数的表达式都能够用来索引数组:
ia2[get_index()]=someVal;
l 多维数组
int ia[4][3];
第一维称为行维,第二维称为列维。row and column。初始化有几种形式:
int ia[4][3]={
{0,1,2},
{3,4,5},
{6,7,8},
{9,10,11}
};
注意:虽然ia[1,2]合法,但表示的是ia[2],访问的是第三行第一个元素。
l 数组与指针类型关系
数组标识符代表数组第一个元素的地址,它的类型是数组元素类型的指针,下面两种形式是等价的,它们返回数组的第一个元素的地址:
ia;
&ia[0];
一个简单的泛型程序设计(generic programming),一个简单的例子:
#include<iostream>
#include<string>
using namespace std;
template <class ElemType>
void print(ElemType *begin,ElemType *end){
while(begin!=end+1){
cout<<*begin<<" ";
begin++;
}}
void main(){
double a[4]={1.1,2.3,4.5,6.7};
int b[3]={1,2,3};
string c[2]={"hello","world"};
print(a,a+3);
cout<<endl;
print(b,b+2);
cout<<endl;
print(c,c+1);
cout<<"\n c[0]:"<<c[0].size()<<"; c[1]:"<<c[1].size()<<endl;}
l vector容器类型
vector类为内置数组提供了一种替代表示,通常建议使用vector。与string类一样,vector也是标准c++引入标准库的一部分。
#include<vector>
1. 数组习惯
模仿内置数组的用法,定义一个长度已知的vector:
vector<int>ivec(10);
//与int ivec[10]类似
而且可通过下标来访问vector。
对于vector的元素的初始化可以调用该类的缺省构造函数获得。还可以为每个元素提供一个显示的初始值来完成初始化:
//定义ivec,包含十个int型元素,每个元素//为-1:
vector<int>ivec(10,-1);
我们不能像数组那样显式的初始化vector,但可以将vector初始化为一个已知数组的全部或一部分,只需要指出用来初始化vector的数组的开始地址以及数组最末尾元素的下一个位置来实现:如:
//把ia6个元素拷贝到ivec
vector<int>ivec(ia,ia+6);
//拷贝3个元素
vector<int>ivec(&ia[2],&ia[3]);
vector可以被另一个vector初始化或赋值:
vector<string>svec;
vector<string>user_name(svec);
svec=user_name;
2. STL习惯
不定义一个大小已知的vector,而是定义一个空的vector:
vector<string> text;
然后往里面插入元素,而不再式索引元素,以及向元素赋值。例如:push_nack()操作,就是后面插入一个元素。
可以继续用下标来迭代访问元素,但更典型做法是用vector操作集中的begin()和end()所返回的迭代器(iterator)
vector<int>ivec(10);
任何一个插入动作是增加一个元素,而不是覆盖了某个现有的元素。一个简单的例子如下:
#include<iostream>
#include<string>
#include<vector>
using namespace std;
void main(){
string word;
vector<string>text;
while(cin>>word&&word!="*"){
text.push_back(word);
}
cout<<"用下标输出为:\n";
for(int i=0;i<text.size();i++)
cout<<text[i]<<" ";
cout<<"\n用类的操作函数输出:"
<<endl;
//iterator 是标准库中的类,具有指针的功能
//*it是对迭代器的引用,并访问实际对象
for(vector<string>::iterator it=text.begin();it!=text.end();it++)
cout<<*it<<" ";
}
#include<iostream>
#include<string>
#include<vector>
using namespace std;
void main(){
vector<string>a(5);
int i=0;
string word;
while(i<5&&cin>>word){
a[i]=word;
i++;
}
cout<<"数组的内容是:"<<endl;
for(i=0;i<a.size();i++)
cout<<a[i]<<" ";
cout<<"\n长度是: "<<a.size()<<"\n插入\"hello world\"后:"<<endl;
a.push_back("hello world!");
cout<<a.size();
cout<<"数组的内容是:"<<endl;
for(i=0;i<a.size();i++)
cout<<a[i]<<" ";
}
习题:
bool isEqual(const int *ia,int iaSize,const vector<int> &ivec){
if(iaSize==ivec.size())
return true;
intlength=iaSize>ivec.size()?ivec.size():iaSize;
cout<<"这两个数组的长度是不相等的,它们公共长度是:"<<length<<",而按照定义,";
//以下这段不了解
for(vector<int>::const_iterator it=ivec.begin();it!=ivec.end()&&length-->0;it++,ia++)
if(*it!=*ia)
return false;
return true;}
l typedef 名字
typedef机制为我们提供一个通用的类型定义设施,可以用来为内置的或用户定义的数据类型引入助记符号。
typedef int *Pint;
typedef char *cstring;
extern const cstring cstr;
//?P104
l volatile限定修饰符
用法与const类似——作为类型的附加修饰符。主要目的是提示编译器,该对象的值可能在编译器未检测到的情况下被改变,因此编译器不能武断的对引用这些对象的代码作优化处理。
l pair类型
#include<utility>
pair是标准库的一部分,使得我们可以在单个对象内部把相同类型或不同类型的两个值关联起来。
pair<string,string> author(:james”,”joyce”);
创建一个pair对象,包含两个字符串,并且初始化。可以用成员访问符号(member access notation)访问pair中的单个元素,它们名字分别是first和second。例如:
author.first==”james”;
author.second==”joyce”;
typedef pair<string,string> Author;
//可以定义大量的相同的pair类型的对象。