《C++ Primer》第2章 变量和基本类型(上)

2.1 基本内置类型

C++基本内置类型包括:算数类型空类型(void)

空类型仅用于一些特殊场合,如函数不返回任何值时使用void作为返回类型。

2.1.1 算数类型

算数类型包括整型(包括字符和布尔型在内)和浮点型

C++标准只规定了每种算数类型尺寸的最小值,同时允许编译器赋予这些类型更大的尺寸。所以算数类型的尺寸在不同机器上有所差别。

布尔类型(bool)的取值是真(true)或假(false)。

一个char的大小和一个机器字节一样

//在本机上用以下程序测试各类型所占字节
#include <iostream>
int main(){
   
    std::cout <<sizeof(int)<<"," //int占4个字节 
              <<sizeof(char)<<","//char占1个字节
              <<sizeof(double)<<","//double占8个字节
              <<sizeof(bool)<<","//bool占1个字节
              <<sizeof(wchar_t)<<"," //wchar_t占4个字节
              <<sizeof(char16_t)<<"," //char16_t占2个字节
              <<sizeof(char32_t)<<"," //char32_t占4个字节
              <<sizeof(short)<<"," //short占2个字节
              <<sizeof(long)<<"," //long占8个字节
              <<sizeof(long long)<<"," //long long占8个字节
              <<sizeof(float)<<","//float占4个字节
        	  <<sizeof(long double) //long double占16个字节
              <<std::endl;
    return 0;
}
/*编译运行此程序,输出为: *4,1,8,1,4,2,4,2,8,8,4,16 *各数字依次对应为程序中各数据类型所占字节数 */

计算机可寻址的最小内存块称为字节

大多数机器,1个字节=8个二进制位,即1 byte=8 bit。

为了赋予内存中某个地址明确的含义,必须知道存储在该地址的数据的类型。

带符号类型和无符号类型

除去布尔型扩展字符型(除char以外的其他字符型,如wchar_t等),其他整型可以划分为带符号的(signed)无符号的(unsigned)

char外,其他默认都是带符号的,前边加上unsigned就变成无符号的。如int为带符号的,unsigned int为无符号的。另外unsigned int可以缩写为unsigned

单独提一下字符型charsigned charunsigned char是不同的。但表现形式只有带符号的无符号的两种,其中的char有没有符号由编译器决定。

double和float尽量选double。因为float通常精度不够,且与double计算代价相差无几。

2.1.2 类型转换

  • 当我们把非布尔类型的算数值赋给布尔类型时,初始值为0则结果为false,否则结果为true。

    如:bool b = 42;,则b的值为true。

  • 当我们把布尔值赋给非布尔类型时,初始值为false则结果为0,初始值为true则结果为1。

    如,接上:int i = b;,则i的值为1。

  • 当把浮点数赋给整数类型时,会进行近似处理,结果值仅保留小数点之前的部分。

    如:double pi = 3.14;,则pi的值为3.0。

  • 当把整数值赋给浮点型时,小数部分记为0。如果该整数所占空间超过浮点型容量,精度可能有损失。

  • 当赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数

    如:8 bit的unsigned char可以表示0至255内的值。若给它赋值-1,则结果为-1对256取模后所得的余数,即255。

    取模计算过程:

    1.求整数商c(向负无穷方向舍入)

    ​ c=a/b=-1/256=-0.00390605,向负无穷方向舍入,则结果为-1。

    2.计算模或余数r

    ​ r=a-c*b=-1-(-1)*256=255。

  • 当赋给带符号类型一个超出它表示范围的值时,结果是未定义的。此时,程序可能继续工作、可能崩溃,也可能产生垃圾数据。

含有无符号类型的表达式

  • 当一个算数表达式中既有无符号数又有int值时,那个int值就会转换成无符号数。

    如:unsigned u =10; int i = -42;,若计算u+i,其结果会是4294967264(前提int占4个字节)。

    计算u+i时,i转换为无符号数,转换过程就是上述取模计算过程。

    1.c=-42/(2^32),向负无穷方向舍入后得c=-1。

    2.r=-42-(-1)*2^32=4294967254

    3.u+i=u+r=4294967264

  • 当从无符号数中减去一个值时,不管这个值是不是无符号数,我们都必须确保结果不能使一个负值。

    如:unsigned u1=42,u2=10;,若计算u2-u1,其结果又会是4294967264。

提示:表达式里既有带符号类型又有无符号类型,带符号数会自动转换成无符号数。

2.1.3 字面值常量

整型、浮点型、字符型、字符串型字面值

一个形如42的值被称为字面值常量,即一望而知的值。

如:42是一个整型字面值。

3.14159是一个浮点型字面值。

'a’是一个字符字面值

“A”是一个字符串字面值。(实际长度为2,因为它以’\0’结尾)。

如果两个字符串字面值位置紧邻且仅由空格、缩进和换行符分隔,那它们实际是一个整体。如:

std::cout<<"a really,really long string literal "
		   "that spans two lines."<<std::endl;

转义序列

有两类字符程序员不能直接使用:一类是不可打印的字符,如退格等;另一类是C++中有特殊含义的字符(单引号、双引号、问号、反斜线)。

在这些情况下需要用到转义序列

\n代表换行符,\r代表回车符,等等。

在程序中,转义序列被当作一个字符使用。

泛化转义序列:形式为\x后紧跟1个或多个十六进制数字,或\后紧跟1~3个八进制数字。其中数字部分表示字符对应的数值。

指定字面值的类型

通过添加前缀和后缀,可以改变整型、浮点型和字符型字面值的默认类型。

L'a'为宽字符型字面值,类型为wchar_t

3.14159L为扩展精度浮点型字面值,类型为long double

42ULL为无符号整型字面值,类型为unsigned long long

布尔字面值和指针字面值

truefalse是布尔类型的字面值,nullptr是指针字面值。

2.2 变量

变量对象两个词一般可以互换使用。

2.2.1 变量定义

基本形式:类型说明符,随后紧跟一个或多个变量名,其中变量名以逗号分隔,最后以分号结束。定义时还可以为一个或多个变量赋初值。如:

int sum=0,value;
Sales_item item;
std::string book("0-201-78345-X");

通常,对象指具有某种数据类型的存储空间。我们在使用对象这个词时,并不严格区分是类还是内置类型。

初始值

  • 用于初始化变量的值可以使任意复杂的表达式

  • 当一次定义两个或多个变量时,对象名字随着定义也就马上可以使用了。因此,在同一条定义语句中,可以用先定义的变量值去初始化后定义的其他变量。如:

    double price = 10.99 , discount=price*0.2;
    

    在C++中,初始化赋值是两个完全不同的操作。

    初始化:创建变量时赋予其一个初始值。

    赋值:把对象的当前值擦除,而以一个新值来替代。

列表初始化

列表初始化:无论初始化对象还是某些时候为对象赋新值,均可采用一组由花括号括起来的初始值。如:

int units_sold{
   0};

值得注意的是,当用于内置类型的变量时,如果使用初始化列表且初始值存在丢失信息的风险,则编译器会报错。如:

long double ld = 3.1415926536;
int a{
   ld}; //错误:转换未执行,因为存在丢失信息的风险
int b(ld); //正确:转换执行,且确实丢失了部分值

默认初始化

如果定义变量时没有指定初始值,则变量被默认初始化。此时变量被赋予了“默认值”,默认值到底是什么由变量类型决定,同时定义变量的位置也对此有影响。

  • 内置类型变量未被显式初始化,它的值由定义的位置决定。

    定义于任何函数体之外的变量被初始化为0。

    定义于函数体内部的内置变量类型是不被初始化的,一个未被初始化的内置类型变量的值是未定义的,访问时会引发错误。

    用一下程序测试:

    #include<iostream> 
    int i;
    int fun(int& c,int& d){
          
    std::cout<<c<<" "<<d<<std::endl;
    return 0;
    }
    
    int main(){
          
    int j;
    int k;
    fun(i,j);
    std::cout<<"k的值为:"<<k<<std::endl;
    return 0;
    }          
    

    反复执行这段程序,可以发现只有i的值固定为0,而j和k每次执行结果不同。因为i定义在任何函数体之外,被初始化为0;而j、k都定义在函数体内,是不被初始化的。

    建议初始化每一个内置类型变量。

  • 每个类各自决定其初始化对象的方式。且是否允许不经初始化就定义对象也由类自己决定。如果类允许这种行为,它将决定对象的初始值到底是什么。

    绝大多数类都支持无须显式初始化而定义对象。

    std::string str1;str1非显式地初始化为一个空串。

2.2.2 变量声明和定义的关系

C++支持分离式编译:允许将程序分割为若干个文件,每个文件可独立编译。

如果程序分为多个文件,就要有文件间共享代码的方法,如std::cout定义在标准库,却能被我们的程序使用。

为支持分离式编译,C++将声明定义区分开来。

声明:使名字为程序所知,一个文件如想使用别处定义的名字则必须包含对那个名字的声明。

定义:创建与名字关联的实体。

区别定义除了规定了变量的类型和名字,还会申请存储空间,并可能为变量赋初始值。而声明仅仅指明变量类型和名字。

  • 只声明不定义的话,就在变量名前加关键字extern。如:

    extern int i;  //声明i而非定义i
    int j;  //声明并定义了j
    
  • 任何包含了显式初始化的声明即称为定义。即extern语句若包含初始值就不再是声明,而是定义了。如:

    extern double pi = 3.1416;  //定义
    

    1.在函数体内部,如果试图初始化一个由extern关键字标记的变量,将引发错误。

    2.变量能且只能被定义一次,但可以被多次声明。

C++是静态类型语言,其含义是在编译阶段检查类型。编译器负责检查数据类型是否支持要执行的运算,如果不支持,编译器将会报错且不会生成可执行文件。

2.2.3 标识符

  • C++的标识符由字母数字下划线组成。
  • 必须以字母或下划线开头。
  • 标识符长度没有限制,但是对大小写敏感
  • 用户自定义的标识符不能连续出现两个下划线,也不能以下划线紧连大写字母开头。
  • 定义在函数体外的标识符不能以下划线开头。

2.2.4 名字的作用域

作用域以花括号分隔。

名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束。

以以下程序为例:

#include<iostream>
int main(){
   
	int sum = 0;
	for(int val = 1; val <= 10; ++val)
		sum += val;
	std::cout<< "Sum of 1 to 10 inclusive is " 
        	 << sum << std::endl;
	return 0;
}
  • main定义于所有花括号外,拥有全局作用域
  • sum和val拥有块作用域。sum在main函数限定的作用域内;val在for语句限定的作用域内。

建议:第一次使用变量时再定义它

嵌套的作用域

作用域中一旦声明了某名字,它所嵌套的所有作用域中都能访问该名字。同时,允许在内层作用域中重新定义外层作用域已有的名字。

以以下程序为例:

#include<iostream>

int reused = 42;   //reused拥有全局作用域

int main(){
   
	int unique = 0;  //unique拥有块作用域
	//输出#1:使用全局变量reused;输出42 0
	std::cout << reused << " " << unique << std::endl;
	int reused = 0; //新建局部变量reused,覆盖了全局变量reused
    //输出#2:使用局部变量reused;输出0 0
    std::cout << reused << " " << unique << std::endl;
    //输出#3:显式地访问全局变量reused;输出42 0
    std::cout << ::reused << " " << unique << std::endl;
    return 0;
}
  • 输出#1出现在局部变量reused定义之前,因此这条语句使用全局作用域中的名字reused。
  • 输出#2发生在局部变量reused定义后,因此这条语句使用局部变量reused。
  • 输出#3使用了作用域操作符::,覆盖了默认的作用域规则,因为全局作用域本身并没有名字,所以当作用域操作符的左侧为空时,向全局作用域发出请求,获取::右侧名字对应的变量。故这条语句使用的全局变量reused。

如果函数有可能用到某全局变量,则不宜再定义一个同名的局部变量。

全部评论

相关推荐

10-09 22:05
666 C++
找到工作就狠狠玩CSGO:报联合国演讲,报电子烟设计与制造
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务