变量和基本类型

基本内置类型

C++定义了一套包括算数类型和空类型(void)在内的基本数据类型。
算数类型包含了字符,整形,浮点数,布尔值。
空类型不对应具体的值,用于一些特殊场景,比如函数不返回任何值时使用空类型。

算数类型

算数类型分为两类:整形(包括字符和布尔类型)和浮点型

字符和布尔类型其实就是整形,字符型存储的是对应的ASCII值,比尔类型通常可以转为0(假)或者1(真)。

算数类型的尺寸(所占用的内存空间),在不同的机器上有所不同,C++标准规定了类型尺寸的最小值,同时允许编译器赋予这些类型更大的尺寸。类型的尺寸不同,代表的是它能表示的数据范围也不一样。

类型含义最小尺寸
bool布尔类型未定义
char字符8位
wchar_t宽字符16位
char16_tUnicode字符16位
char32_tUnicode32位
short短整形16位
int整形16位
long长整形32位
long long长整形64位
float单精度浮点数6位有效数字
double双精度浮点数10位有效数字
long double扩展精度浮点数10位有效数字

布尔类型的取值是真(true)或者假(false)。
C++提供了集中字符类型,其中多数支持国际化,基本的字符类型是char,一个char的空间确保可以存放机器基本字符集中任意字符对应的数字值。 也就是说,一个char的大小和一个机器字节一样。

机器字长:CPU一次能处理数据的位数,通常与CPU的寄存器位数有关。

其他字符类型用于扩展字符集,如wchar_t、char16_t、char32_t。
wchar_t类型用于确保可以存放机器最大扩展字符集中的任意一个字符。
类型char16_t和char32_t则为Unicode字符集服务。(Unicode用于表示所有自然语言中字符的标准)。
除了字符和布尔类型外,其他整形用于表示不同尺寸的整数。C++语言规定,一个int至少和一个short一样大,一个long至少和一个int一样大,一个long long至少一个一个long一样大。其中,数据类型long long是在C++11中新定义的。

计算机以比特序列存储数据,每个比特非0即1。大多数计算机以2的整数次幂个比特作为块来处理内存。可寻址的最小内存块称为字节(byte),存储的基本单元称为字(word),它通常由几个字节组成。在C++语言中,一个字节要至少能容纳机器基本字符集中的字符。大多数机器的字节由8比特构成。字由32或64比特构成。

浮点型可以表示单精度,双精度和扩展精度值。C++标准指定了一个浮点数有效位数的最小值。然而大多数编译器都实现了更高的精度。通常,float以1个字(32比特)来表示,double以2个字(64比特)来表示,long double以3或4个字(96或128比特)来表示。
一般来说,类型float和double分别有7和16个有效位。

带符号类型和无符号类型

除去布尔型和扩展字符型之外,其他整形可以划分为带符号无符号两种。带符号类型可以表示正数、负数或0,无符号类型则仅能表示大于等于0的值。
类型 intshortlonglong long都是带符号的,通过在这些类型名之前添加unsigned就可以得到无符号类型,例如unsigned long。类型unsigned int可以缩写为unsigned
字符型被分为三种:charsigned charunsigned char。需要注意的是:char类型和signed char并不一样。尽管字符型有三种,但是字符的表现形式却只有两种,带符号的和无符号的。类型char实际上会表现为上述两种形式中的一种,具体那种由编译器决定。
无符号类型中所有的比特都用来存储值,例如8比特的unsigned char可以表示0到255区间的值。
C++约定带符号类型在表示范围内正值和负值应该平衡。所以signed char理论应该可以表示-127到127区间内的值,大多数现代计算机将实际的表示范围定为-128到127。

如何选择类型
  • 当知道数值不可能为负时,选用无符号类型
  • 使用int执行整数运算。
  • 算数运算符中不要使用char或者bool
  • 执行浮点数运算选用double。因为float通常精度不够而且双精度浮点数和单精度浮点数计算代价相差无几。

自动类型转换

对象的类型定义了对象能包含的数据和能参与的运算,其中一种运算被大多数类型支持。就是将对象从一种给定的类型转换为另一种相关的类型。
当在程序的某处我们使用了一种类型而其实对象应该取另一种类型时,程序会自动进行类型转换。如下例:

bool a = 42;  // a = 1
int i = a;  // i = 1
i = 3.99;  // i = 3
double pi = i;  // pi = 3.0
unsigned char c = -1;  // 转为数字输出,值为255 (char占8比特的情况)
signed char c2 = 256;  // c2的值是未定义 (char占8比特的情况)

类型所能表示的值的范围决定了转换的过程:

  • 当我们把一个非布尔类型的算数值赋给布尔类型时,初始值为0则结果为false,否则结果为true。
  • 当我们把一个布尔值赋给非布尔类型时,初始值为false则结果为0,初始值为true则结果为1。
  • 当我们把一个浮点数赋值给整数类型时,结果值仅保留浮点数小数之前的部分。
  • 当我们把一个整数赋值给浮点数时,小数部分记为0,如果该整数所占的空间超过了浮点类型的容量,精度可能有损失。
  • 当我们赋给无符号类型一个超出它表示范围的值时。结果是初始值对无符号类型表示数值总数取模后的余数。例如:8比特大小的unsigned char 可以表示0至255区间内的值,如果我们赋了一个区间以外的值,则实际结果是该值对256取模后所得的余数。因此,把-1赋值给8比特大小的unsigned char 所得的结果是255。
  • 当我们赋给带符号类型一个超出它表示范围的值时/结果是未定义的。此时,程序可能继续工作,可能崩溃,也可能生成垃圾数据。

除了赋值操作,当程序的某处使用了一种算术类型的值而其实所需的是另一种类型的值时,也会发生类型转换。例如我们在if判断的时候,使用了一个int类型的变量,也会自动转换为ture或false,变换的效果和上述一样。

含有无符号类型的表达式

当一个算术表达式中既有无符号数,又有int值时,那个int值就会转换为无符号类型
把int转换成无符号数的过程和把int直接赋值给无符号变量一样。

unsigned u = 10;
int i = -42;
std::cout << i + i << std::endl; // -84
std::cout << u + i << std::endl; // 4294967264 (int占32位的情况)

上面的案例中,第一个计算结果符合预期。但是第二个确不符合正常计算的逻辑。
第二个计算表达式里,相加之前首先把整数-42转换成无符号数。把负数转换为无符号数类似于直接给无符号数赋一个负值,结果等于这个负数加上无符号数的模。(可以看上面的类型自动转换部分)

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

unsigned u1 = 42, u2 = 10; // 都是无符号
std::cout << u1 - u2 << std::endl; // 32
std::cout << u2 - u1 << std::endl; // 结果是负数,所以最终计算结果会有一步负数转换无符号类型的逻辑

无符号数不会小于0这个事实也会关系到循环的写法。例如我们要输出10到0的数字,我们可以这样写:

for (int i = 10; i >= 0 ; --i) 
    std::cout << i << std::endl;

但是如果你用无符号类型来写这个循环,那就成了死循环:

// 错误,死循环
for (unsigned  int i = 10; i >= 0 ; --i) 
    std::cout << i << std::endl;

随着循环的进行,当i = 0进行了最后一次输出之后,执行 --i,之后i = -1,但是注意,我们的 i 是一个无符号类型,所以这里会有一次负数转为无符号类型的转换,也就是最后结果是4294967295,然后继续循环。

总结:
1、无符号数不会小于0,如果我们给一个无符号是赋值一个负数(或者正数超出了表达范围),他都会转换成一个合法的无符号数。这个自动转换可能会导致程序的错误。
2、不要混用带符号类型和无符号类型。

字面值常量

一个形如 42 的值被称作字面值常量,这样的值一看就知道数据是什么。每个字面值常量都对应一种数据类型,字面值常量的形式和值决定了它的数据类型。

整形和浮点型字面值

我们可以将整型字面值写作十进制数、八进制数或十六进制数。以0开头的整数代表八进制,以0x或0X开头的代表十六进制数。
整形字面值具体的数据类型由它的值和符号决定。默认情况下,十进制字面值是带符号数。八进制和十六进制字面值即可能是带符号的也可能是无符号的。十进制字面值类型是intlong、和long long中尺寸最小的那个。前提是这种类型要能容纳下当前的值。八进制和十六进制字面值的类型是能容纳其数值的intunsigend intlongunsigned longlong longunsigned long long中尺寸最小者。如果一个字面值连与之关联的最大数据类型都放不下,将产生错误。
类型short没有对应的字面值。
尽管整形字面值可以存储在带符号数据类型中,但严格来说,十进制字面值不会是负数。如果我们使用了一个形如-42的字面值,那个负号并不在字面值之内,它的作用仅仅是对字面值取负值而已。
浮点型字面值表现为一个小数或以科学记数法表示的指数,指数部分用Ee标识。默认浮点型字面值是double类型。

字符和字符串字面值

由单引号括起来的一个字符为char型字面值,双引号括起来的零个或多个字符则构成字符串型字面值。字符串字面值类型实际上是由常量字符构成的数组。
编译器在每个字符串的结尾添加一个空字符,因此字符串字面值的实际长度要比他的内容多1。
如果两个字符串字面值位置紧邻且仅由空格、缩进和换行符分隔,那么他们实际是一个整体。

#include "iostream"
int main()
{
    std::cout << "hello"    "world" << std::endl;
    
    std::cout << "hello"
                 "world" << std::endl;
    return 0;
}

转义序列

有两类字符不能直接使用:
一类是不可打印的字符,如退格或其他控制字符。因为他们没有可视的图符。
还有一类是在C++中有特殊含义的字符。
这些情况下需要用到转义序列,转义序列均以反斜线作为开始,C++语言规定的转义序列包括:

符号符号名称符号符号名称
\n换行符\t横向制表符
\a报警(响铃)符\v纵向制表符
\b退格符\"双引号
\\反斜线?问号
\’单引号\r回车符
\f进纸符

指定字面值的类型

数值型:

标识位置标识的类型示例
u 或 U后缀unsigned55U
l 或 L后缀long55L
ll 或 LL后缀long long55LL
f 或 F后缀float1.344F
l 或 L后缀long double1.345L

因为小写的 l 和 数字 1 和大写字母 I 很像,所以上面的标识都推荐使用大写字母。
整型的标识可以组合使用,例如 55UL

字符和字符串字面值

前缀含义类型
uUnicode16字符char16_t
UUnicode 32字符char32_t
L宽字符wchar_t
u8UTF-8(仅用于字符串字面常量)char

布尔值和指针字面值

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

变量

变量提供一个具名的、可供程序操作的存储空间。C++中的每个变量都有其数据类型,数据类型决定着变量所占的空间大小,布局方式,该空间能存储的值的范围和变量能参与的运算

变量定义

变量定义的基本形式:首先是类型说明符,后面紧跟着由一个或者多个变量名组成的列表,变量名以逗号分隔。定义时还可以为一个或者多个变量赋初值。

int a = 10; // 定义一个变量并赋初始值
int aa; // 定义一个变量
int b, c, d; // 定义多个变量
int e, f = 10 ,g; // 定义多个变量 并给其中一个变量赋初始值

初始值

当对象在创建的时候获得了一个特定的值,我们说这个对象被初始化了。用于初始化变量的值可以是任意复杂的表达式。
当我们一条语句中定义了多个变量时,后一个变量可以使用前一个变量进行初始化:

#include "iostream"
int demo() {
    return 22;
}

int main()
{
    // b 可以直接使用 a 初始化
    int a = 10, b = a * 2;
    // 可以使用函数的返回值初始化
    int c = demo();
}

初始化和赋值都使用 = 来进行操作,但是他俩并不是同一个动作,初始化时创建变量并赋予一个初始值,而赋值时把对象当前的值擦除, 用一个新值来代替。

列表初始化

C++定义来初始化的好几种不同形式。例如我们想定义一个变量 abc 并初始化为 0 ,以下4种语句都可以:

int abc = 0;
int abc = {0};
int abc(0);
int abc{0}; // C++11新标准,不支持C++11会报错

g++ 指定C++标准:
GCC从版本4.8开始支持C++11,使用-std选项来指定标准,例如:
-std=c++11
-std=c++14
-std=c++17
-std=c++20

作为C++11新标准的一部分,用花括号来初始化变量得到了全面应用。而在此之前,这种初始化方式在某些受限制的场合下才能使用。这种初始化方式称为:列表初始化。无论是初始化值还是给变量赋新值,都可以使用这种方式了。
当用于内置类型的变量时,这种初始化方式有一个重要的特点,如果我们使用列表初始化并且初始值存在丢失信息的风险时,编译器将报错:

long double ld = 3.1415926536;
int a{ld} , b = {ld}; // 报错,存在丢失信息的危险
int c(ld) , d = ld; // 正确:正确执行,并且确实丢失了部分数值

默认初始化

如果定义变量时没有指定初值,则变量被默认初始化,此时变量被赋予了默认值。默认值是什么由变量类型决定,同时定义变量的位置也会对此有影响。
如果是内置类型没有被显示初始化,它的值由定义的位置决定,定义于任何函数体之外的变量被初始化为0,定义在函数体内部的内置类型变量将不被初始化。一个没有被初始化的内置类型变量的值是未定义的。
每个类各自决定初始化对象的方式,是否允许不初始化就定义对象也由类自己决定。如果类允许这种行为,它将决定对象的初始值到底是什么。例如string类,std::string empty; 没有指定初始值,则默认初始化为一个空串。

未初始化的变量含有一个不确定的值。使用这种值很可能会引起程序运行错误。所以推荐初始化每一个内置类型的变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值