《TC++PL》第四章笔记——类型和声明

本文深入探讨C++中的各种类型,包括基本类型、枚举、void类型等,并解析声明与定义的区别,以及如何正确使用C++中的各种类型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1Accept nothing short of perfection.

Perfection is only achieved on the point of collapse.

  对这些话可以有多种理解,但可以猜得出来,这里说的完美指的是类型清楚,没有歧义,便于阅读,可以执行好等等。的确,这些都和一种强类型的语言(比如C++)和“完美的”声明和定义有关。

 

2、类型是什么东西

  对于一个典型的C++表达式 x = y + f(2),要使其中的 xyf 有意义,就必须有合适的声明。如第二章所言,声明为程序引进了一个名字,并对它能执行什么样的操作做出规定。即每个名字都关联了一个类型——这个类型决定了可以对这个名字应用什么操作,并决定这些操作将如何做出解释。

  程序员必须描述这些实体的存在性。

 

3、基本类型

  先是一些老生常谈的东西——bool char int double enum void 等等,没什么可说的。但这里有些分类很有意思:布尔量、字符和整数类型合称整型( integral type )。整型和浮点类型一起成为算术类型。枚举和类被称为用户定义类型——不能事先没有声明就直接使用。与用户定义类型相反,其他类型都被称为内部类型。

  对于大部分应用,使用boolcharintdouble就足够了。其它基本类型都是为优化或特殊需要而提供的变化,在真正需要之前最好是忽略之。

 

4、布尔量

  C++里提供了bool类型,用来表示真假。与C语言里惯用int相比,它在逻辑上更直观。它最普遍的用途就是谓词的结果类型。

  按照定义,true 的值为1,而false 的值为0。整数、指针都可以隐式的转换为bool

  在算术和逻辑表达式里,bool都将转换为int进行运算。

 

5、字符类型和字符文字量

  类型为char的变量可以保存具体实现所用的字符集里的一个字符。加上黑体的限定是很有必要的,因为这与本地化和可移植性有着密切的关系。

  避免有关字符数值的不必要假设。这是BS做出的一条忠告。我们不能因为自己的机器上使用的是ASCII字符集而把某个字符等价为一个整数数值。这是依赖于实现的东西,会限制程序的可移植性。甚至连char是否有符号都依赖于具体的系统,只是在0127的范围内,我们觉察不出来而已。

  如果真的需要把整数存入char中,需要关注更细节的知识。这里不作赘述,可参见书后附录。

  关于wchar_t类型:用于保存像Unicode这样的更大的字符集。它是一个独立的类型。MSDN上作出的解释是:wchar_t: internal type of a wide character. Useful for writing portable programs for international markets.

  字符文字量:其形式就是单引号括起的一个字符。如’a’,’0’,类型为char这样的字符文字量实际上是一种符号常量。——这是BS所说的一句很深刻的话。这种文字量表示了这台机器上的字符集里面该字符的整数值。这样它就在源代码级别上提高了程序的可移植性。初次之外,转义字符也是一种字符文字量。宽字符的文字量形式是L’ab’,类型为wchar_t

 

6、整数类型

  int unsigned signed 以及 longshort等组合使用。注意:从unsignedsigned常会引起转换问题。

  整数文字量:有十进制、八进制(0开头)、十六进制(0x开头)的文字量。编译器应该能够对文字量过长,无法表示的情况给出警告。在我的vc编译器中,如果给出

int veryLong = 11223344556677889900;

编译器就会给出一个warning

warning C4305: 'initializing' : truncation from 'const unsigned __int64' to 'int'

而我的机器上所支持的最大文字量就是 unsigned __int64(0xffffffffffffffff = 18446744073709551615)

 

因此,如果我给出

unsigned __int64 UnexpectedLong = 18446744073709551616;

就会导致一个Error

error C2177: constant too big

 

但是,我如果把“同样”的意思写成

unsigned __int64 UnexpectedLong = 18446744073709551615 + 1;

的话,就没有编译错误了,同时,只得到了一个warning

warning C4307: '+' : integral constant overflow

呵呵,溢出不算编译错误的。忽略这个警告,运行程序发现UnexpectedLong == 0。果不出所料。这个例子是否能帮助理解编译的过程?呵呵。关于大小的更详细讨论在后面。

 

八进制和十六进制特别适合用于表示二进制位的模式。

后缀UL——表示文字量是unsigned long型。

限制使用意义不明显的文字量——只用它们来初始化const或者enum ——单点维护原则。

 

7、浮点类型

   floatdoublelong double,确切意义由实现决定。——参见IEEE的标准。

浮点文字量中间不允许出现空格。可以加后缀fL来表示floatlong double

 

8、大小问题

C++基本类型的某些方面是由实现确定的。大小就是这样的。只是按照C++的定义,char的大小为1,其它类型的大小并没有规定死(这点不像Java)。只是给出了一种原则。

不要假定类型的大小。依赖于实现的这些特征都可以在<limits>里找到。本来想自己编码查看我自己系统的内部类型属性,但看到MSDN上有现成的,我就偷懒粘过去。改动了一点代码(把float改为double)就运行了。

不过这次有一个意外的收获——发现我的“实现”里面doublefloat都有表示“无穷大”的值。这个值就是1.#INF000000000000,但奇怪的是,它不能作为文字量出现在源代码里。现在我只能通过这个方法“得到”这个无穷大:

double inf = numeric_limits<double>::infinity();

除了无穷大以外,浮点数里面还有一个很特殊的值:NaNNot a Number),在网上找了一点它的用法。大致是说,这是一个非法操作得到的值(但不是无穷大),或是一个不可忽略的值(不懂什么意思,姑且言之)。NaN分为quietsignaling两种。我的系统上,quiet NaN的表示是-1.#IND,而signaling NaN的表示是 -1.#INF。例如下面的一句代码:

cout << sqrt(-1);

将输出-1.#IND

还有一点需要注意,NaN是不能比较的,它不等于任何数,甚至也不等于自己。也就是说,表达式( NaN == NaN )的值是false

小正数(The epsilon for double is 2.22045e-016epsilon也是很有用的。简单的说,它可以用来在判断条件中消除“数字噪音”(不精确的浮点运算给判断带来的误差)的影响。

另:保值问题。保值的转换不会引起任何问题。非保值转换应尽量避免。

 

8void类型

没有void对象,但函数返回值和参数列表都可以用这种类型。void* 也很有用,它是指向不明对象的指针,在windows编程中经常用来作句柄。

 

9、枚举

枚举类型用来保存一组用户刻画的值。大多数程序中,他们用来表示离散的东西。书上有例子:enum keyword{ ASM, AUTO, BREAK }; 枚举最常用的地方在于和switch组合来进行分支。BS说上例中如果三个keyword值中只有两个被switch处理,编译器有可能给出一个警告。但我的vcDev-c++都没有对此提出任何警告。

一个枚举的表示范围就是能使所有枚举值位于此范围内的2的幂。枚举的sizeof不会大于sizeof(int)。在我的两个编译器上,这个值应该就是sizeof(int)

 

10、声明和定义

声明为一个名字关联一个类型。而定义说明了有关名字所引用的那个实体。经常使用的大多数声明也同时是定义。但以下情况例外(恐怕不只这些情况):

double sqrt(double); //函数原型声明

extern int error_number;  //extern声明

struct User;  //自定义类型声明——定义在别处

每个命名实体必须恰好有一个定义(有且只有一个,预编译语句里面的#ifndef就是为此而设计)。可以有多个声明,但所有声明必须在所引用的类型上完全一致。所以

int count;

int count;

是重复定义的错误。

extern int error_number;

extern short error_number;

是类型不匹配的错误。而

extern int error_number;

extern int error_number;

没有错误。

定义也可以为实体确定一个“值”。任何描述了初始值的声明都是一个定义。

 

11、声明的结构

一个声明由四部分组合而成:一个可选的“描述符”、一个基础类型、一个声明符,还有一个可选的初始式。

描述符说明了被声明事物的某些非类型的属性。

声明符由一个名字和若干可选的声明运算符组成。声明运算符一般有:

*指针、*const常量指针、&引用——前缀

[ ]数组、( )函数——后缀

后缀的声明运算符比前缀的那些约束力更强。例如*kings[ ]是一个数组,而且是一个指针的数组。要改变个结合顺序,就要使用括号。

注意,声明中要明示类型。当然,在vc中,像const c = 7;这种定义是合法的,但尽量不要用。(Dev-C++中为非法)——隐式的int是许多错误的根源。

一条语句声明多个名字时,声明运算符只作用于一个单独的名字。这种写法不利于阅读,因此应该避免。

 

12、名字

标识符的命名原则已经没什么可说的了,有意思的是 “____” 也是一个合法的标识符。这并不奇怪,但很新鲜。不过,这也太丑陋了。另外,尽量避讳以下划线开头的标识符,那是留给编译器和库的东西。

不要用让人看不清的标识符。较大作用域和较少使用的名字应当长一点。较小作用域和较多使用的名字应当短一点。名字的选择应当反映它的意义而不是实现。宏全部用大写。(最好不用宏)

另:尽量用大小写交错的“驼峰式”命名法,这样会使标识符很漂亮。(不止是漂亮)

 

13、作用域和遮蔽

遮蔽是很有意思但没有太大意义的技术细节,即使知道它的规则也要尽量避免名字的重复。一个名字的作用域从它被声明的那点开始——声明符结束之后,初始式开始之前。(但要注意,这个之后、之前的概念就是指在源文件中出现的顺序,和程序执行的顺序无关)

 

14、初始化

静态对象自动初始化为适当类型的0。自动对象和动态分配的对象没有默认初始化。

 

15、对象和左值

变量名字说到最后就是引用的地址。一个对象就是存储中一片连续的区域。左值就是引用某个对象的表达式。左值不一定能放在赋值号左边。没有声明为常量的左值称为可修改的左值。MSDN上这样解释左值和右值:

L-values appear on the left side of an assignment statement (hence the “l” in l-value). Variables that would normally be l-values can be made nonmodifiable by using the const keyword; these cannot appear on the left of an assignment statement. Reference types are always l-values.

The term r-value is sometimes used to describe the value of an expression and to distinguish it from an l-value. All l-values are r-values but not all r-values are l-values.

Note   The examples in this section illustrate correct and incorrect usage when operators are not overloaded. By overloading operators, you can make an expression such as j * 4 an l-value.

最后的那个Note很有意思。当重载了运算符以后,我们就能把某个右值表达式变为左值表达式。

 

16typedef的作用

可以把很长很丑的名字变短。

可以明确某些类型的作用。(typedef unsigned int Count

可以在源代码级别上增强可移植性,使程序趋向于单点维护。

 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值