TYPES AND DECLARATIONS
类型与声明
1.
C++有一组基础类型,对应于计算机里的一些常见的基本存储单元和一些利用这些存储单元保存数据的基本方法
Boolean,character,integer 统称为整形
整形和float point(浮点型)统称为算术类型
class和enum被称为用户定义类型,其他的叫做内部类型
2.
BOOLean
bool b=a==c
bool最常用的就是作为一个测试某种情况的函数的返回值的类型
bool greater(int a,int b){return a>b;}
bool转化为int时 true的值为1,false为0
integer也可以隐式得转化为bool,非零值为true,零值为false
在算术和逻辑表示式中,bool都是被转化为int来进行算术和逻辑运算
pointer(指针)也可以隐式得转化为bool,同int
3.
character
一般来说,char的长度为8bits,所以表示的是256个不同值中的一个
支持不同的自然语言的字符集之间以及相同自然语言的不同表达方式之间存在着很大的差异
这里只关心这些差异是如何影响C++的规则的
作出一些假定是不安全的,例如
假定字符集里只有不超过127个字符
不超出英语的字符(欧洲某些字符集将提供更多)
字母字符是连续的(EBCDIC i和j之间有空隙)
写C++的每个字符都是可用的(有些字符集不提供{}[]等)
每一个char类型的值都有一个整形的值 例如ASCII码中的b字符是98
关于char是有符号数还是无符号数,是由实现决定的,C++中提供了两种unsigned char和signed char,差异只存在于0-127之外,幸运的是,大部分的字符都在0-127之间
C++还提供了wchar_t用来提供更大的字符集,例如Unicode,这是一种不同的类型,大小由实现决定。wchar_t是一种typedef,后缀_t是为了区分内部类型和typedef类型
char是一种整形,所以可以进行算术和逻辑运算
文字常量用单引号表示,文字常量实际上是 你所运行C++的机器上的字符集 中该字符的 整型值 的 符号表示
例如 如果你在用ASCII码为字符集的机器上运行 那么'0'的值就是48
宽字符型为 w_char 型 定义方式为L'ab' 例子
#include <iostream>
using namespace std;
int main()
{
locale loc("chs"); //定义“区域设置”为中文方式
wcout.imbue(loc);//载入中文字符输入方式
wchar_t str[]=L"中国";//定义宽字符数组,注意L是大写
wcout<<str<<endl;//显示宽字符数组,下同
}
结果为
中国
4.
Integer
不像char,int默认的是signed int
后缀U和后缀L
分别表示unsigned和long
3是int型 3U是unsigned型 3L是long型
5.
float
分为float double long double
对于不同的问题选择合适的精度需要对浮点运算的理解
不清楚的时候用double
常量默认的类型为double
1.23e-10
中间不允许有空格,如果想得到一个float类型的,用后缀f
3.14159f
6.
size
C++的基本类型的某些方面是有 实现 决定的,例如一个int类型的大小。为长远和移植性考虑,必须考虑这个问题,不同机器上或者不同编译器上是不同的。
C++里对象的大小是通过char的大小的整数倍来表示的
定义size of a char 是1
1 = sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long)
1<=sizeof(bool) <= sizeof(long)
1<=sizeof(wchar_t) <= sizeof(long)
1<=sizeof(float) <= sizeof(double) <= sizeof(long double)
sizeof(N) = sizeof(signed N) = sizeof (unsigned N)
N可以是char int short long
另外还保证char至少有8bits,short至少有16bits,long至少有32bits
char将存储这个机器上字符集中的一个字符,一般来说是8bits,int,一般来说是4byte(32bits) 更多的假定是不安全的:)
依赖于实现的特征可以再<limits>中查到,例子:
#include <limits>
int main()
{
cout<<"largestfloat== "<<numeric_limits<float>::max()<<'/n'
<< ", char is signed== "<<numeric_limits<char>::is_signed<<´/n´;
}
我的vc6.0的结果是
largest float== 3.40282e+038
char is signed== 1
Press any key to continue
在赋值和表达式里你可以随意混合这些基础类型,如果一个变量v的值能用一个T类型的变量正确得表示,那么v到T的转换就是保值的,对于不保值的转换我们应当尽量避免。
试着在细节上去理解隐式转换
7.
void
void x;//error
void f(){};//f()没有返回值
void *p;//指向未知类型的指针
为了加强语法的规范性,请用 void f(){}
8.
enum
枚举类型属于用户自定义类型,是能够存储用户指定的一组值,被定义之后,一个枚举类型用起来很像一个整形。
命名了的整形常量可以被定义为枚举的一个成员
enum {ASM,AUTO,BREAK};
ASM,AUTO,BREAK叫做enumerater,这是三个整形常数,默认情况分别被赋值为0,1,2
一个枚举enumeration可以被命名
enum keyword {ASM,AUTO,BREAK};
enumerater的类型就是它的enumeration,上例中ASM的类型就是keyword
将变量声明为enum而不是int可以给用户和编译器一些关于这个变量的用途的提示
一个enumerater可以被整形的常量表达式所初始化
enumeration的表示范围视其中是否有非零值以及绝对值最大的数值而定
enum e1{dark,light};//range: 0:1
enum e2{a=3,b=9};//range: 0:15
enum e3{min=-10,max=1000000};//range: -1048576:1048575
一个整型值可以被 显式 得转化成enumerater,但是必须在enumeration的range之内才有意义
enum flag {x=0,y=1,z=4,e=8};// range: 0:15
flag f1=5;//error: 类型不匹配,需要 显示 转换
flag f2=flag(5);//正确,类型匹配且在range之内
flag f3=flag(z|e);//正确,常量表达式z|e的值为12
flag f4=flag(99);//未定义,out of range
一个enum类型的大小是某种足够支持它的表示范围的整形的大小,但是不会大于 sizeof(int) ,也就是说在一个int型大小为4byte的机器上,e1的大小可能是1,也可能是4,但是绝对不可能是8或者任何比4大的值。。。
9.
declarations
声明
一个变量在程序中使用之前必须声明,必须指定它的类型来通知编译器这个名字代表的是哪种实体。
char ch;
string s;
int count=1;
const double pi=3.1415926535897932385;
extern int error_number;
char * name="Njal";
char * season[]={"spring", "summer", "fall", "winter"};
struct Date {int d,m, y;};
int day (Date *p) {return p->d;}
double sqrt (double);
template <class T> T abs(T a) {return a<0?-a:a;}
typedef complex <short> Point;
struct User;
enum Beer{Carlsberg, Tuborg, Thor};
namespace NS{int a;}
如例子所示,声明declare所作的事情可以并不仅仅是把一个名字和一个类型相关联。
这些声明中的大部分还是 定义,也就是说他们还为这个名字指定了 相应的实体。
对于ch来说,这个 实体 就是适当数量的存储空间,以使它可以作为一个变量来使用,这些内存将被分配。
对于day,被指定为一个函数。pi被指定为3.14............。Date是一个新的类型。Point被指定为complex<short>的同义词。
上例中只有
double sqrt (double);
struct User;
extern int error_number;
这三个声明没有同时被定义
在C++中一个名字只能被 定义 一次!但是可以被 声明 多次,但是它所引用类型必须一致!
int count;
int count;//error,重复定义
extern int error_number;
extern short error_number;//error,类型不一致
extern int error_number;
extern int error_number;//正确,没有问题,尽管这毫无意义
某些定义为它们定义的空间赋了某个 值,例如
struct Date {int d,m, y;};
int day (Date *p) {return p->d;}
const double pi=3.1415926535897932385;
typedef complex <short> Point;
这些在定义之后是不能改变的,而其他的一些可以在后面的程序中改变,例如
int count=1;
char * name="Njal";
上述所有的 定义 中,只有
char ch;
string s;
没有给定相应的值。
任何给了一个 相应值 的声明都是一个 定义。
10.
声明的结构
一个声明包含4个部分,一个可选的 描述符,一个基础类型,一个声明符,一个可选的初始化
除了函数和命名空间以外,声明都要以 ; 结尾
例如
char *name[]={"Tom","Jack","Lucy"};
char 是基础类型,*name[]是声明符,=后面的是初始化
描述符例如extern virtual,用来说明一些非类型的特征
声明符由一个名字和可选的操作符组成,常见的操作符
* pointer prefix
*const constant pointer prefix
& reference prefix
[] array postfix
() function postfix
后缀比前缀有更强的约束力
例如 *char1[]是一个指针数组,而不是一个数组指针
不能没有 基础类型和声明符!
11.
声明多个名字
int *a,b[10],c;
operator只作用于一个declarator
不提倡这种写法。
关于name
开头应该是letter,下划线被当作letter来看待。
以下划线开头的名字不提倡使用,区分大小写!!!!!!
一个较大作用域的名字应该是相对比较长的名字,比如vector,window_with_border,Department_number
经常使用的名字应该是比较短的,选择反应它的实体的意义的名字,而不是它的实现,例如用phone_book,而不用number_list
Choosing good name is an art.
保持一种命名风格,例如非标准库的类型都首字母大写,非类型的都小写,例如 Shape,current_token
宏,都大写,例如 HACK,在一个标识符中用下划线来分隔单词
12.
Scope
作用域
一个声明将一个名字引入到一个作用域中,一个名字只在程序中指定的部分起作用。
对于一个在函数里声明的名字,通常叫做局部名字,作用域是从它被声明之处到它所在的block结束的地方
一个block就是{}之间的一段
如果一个名字在任何的function,class,namespace之外定义,那么这个名字就叫做global,全局变量
它的作用域从它被声明之处到这个文件结束
一个局部名字的声明可以屏蔽掉它外围的block的名字
名字遮蔽是不可避免的,但是应该尽量避免
一个被遮蔽的全局名字可以用作用域标识符 :: 来引用,例如
int x
void f(){
int x=1;
::x=2;
cout<<x<<'/n'<<::x;
}
结果是
1
2
没有办法引用被遮蔽的局部名字
一个名字的作用域开始于它声明的那个点,也就是说在声明之后,初始化之前,这也就意味着,可以用这个名字本身来初始化它,例如
int x;
void f()
{
x=x;
}
这样做是合法,但是是荒谬的。。。。
函数的参数被认作是在函数的最外层声明
void f(int x);
{
int x;
}
这样做是错误的,重复声明
13.
Initialization
初始化
如果没有指定初始化,那么一个全局的或者命名空间的,或者一个被指定为static类型的局部对象(统称为静态对象)被初始化为相应类型的0值,局部变量(自动对象)或者动态创建的对象不进行默认初始化。
14.
对象和左值
一个名字所需要的条件就是,它是 内存中的某些东西 ,这也是一个对象的最简单的最基本的概念。也就是说,一个对象是一个连续的存储空间,一个左值是引用这个对象的表达式。不是所有的左值都可以放在赋值语句的左边,一个左值可能指向一个常量。不用把这种基础的对象和类的对象以及多态类型的对象所混淆。
除非程序员指定,一个函数里声明的对象从它被定义开始创建,出了作用域的时候销毁。这样的对象叫做动态对象。
全局或者在命名空间里声明的对象或者是在函数里被声明为static的局部变量只被创建和初始化一次,并且到程序结束一直生存。这样的对象叫做静态对象。
用new和delete操作符可以直接控制对象的生命周期。
15.
typedef
typedef是一个类型的同义词,不是为一个类型指定一个相应的变量。
typedef char* Pchar;
Pchar ch1,ch2;
char* ch3=ch1;
一个名字可以用typedef定义为一个类型的 简短写法 。
另一种用途就是 例如
typedef int int32;
typedef short int16;
如果你在你的程序中可能用到较大整数的地方采用int32类型,那么当你的程序移植到一个sizeof(int)=2的机器上的时候,只要
typedef long int32;
typedef定义的只是一个同义词,并不是一个独立的类型,如果想要一个独立的类型,参照enum,class
练习题,机器中的size
sizeof(bool) = 1
sizeof(char) = 1
sizeof(signed char) = 1
sizeof(unsigned char) = 1
sizeof(unsigned short) = 2
sizeof(short) = 2
sizeof(unsigned short) = 2
sizeof(int) = 4
sizeof(unsigned int) = 4
sizeof(long) = 4
sizeof(unsigned long) = 4
sizeof(float) = 4
sizeof(double) = 8
sizeof(long double) = 8
sizeof(int *) = 4
sizeof(int (__cdecl*)(void)) = 4
sizeof(void (__thiscall Polymorph::*)(void)) = 4
sizeof(void *) = 4
sizeof(enum Bit) = 4
sizeof(enum Intensity) = 4