第二章 变量和基本类型
第一节 基本内置类型
C++
标准规定了算术类型尺寸的最小值,同时允许编译器赋予这些类型更大的尺寸。
比如:
类型 | 含义 | 最小尺寸 |
---|---|---|
bool |
布尔类型 | 未定义 |
wchar_t |
宽字符 | 16位 |
char16_t |
Unicode 字符 |
16位 |
char32_t |
Unicode 字符 |
32位 |
short |
短整型 | 16位 |
基本的字符类型是char
,一个char
的空间应确保可以存放机器基本字符集中任意字符对应的数字值。即一个char
的大小和一个机器字节一样。
其它字符类型用于扩展字符集,如wchar_t
、chat16_t
、char32_t
。wchar_t
类型用于确保可以存放机器最大扩展字符集中的任意一个字符;类型char16_t
和char32_t
则为Unicode
服务(Unicode
是用于表示所有自然语言中字符的标准)。
C++
语言规定一个int
至少和一个short
一样大(大指的是最大位数),一个long
至少和一个int
一样大,一个long long
至少和一个long
一样大,其中,数据类型long long
是在C++11
中新定义的。
大多数计算机以2的整数次幂个bit
作为块来处理内存,可寻址的最小内存块称为字节(byte)
。存储的基本单元称为字(word)
,通常由几个字节组成。在C++
语言中,一个字节要至少能容纳机器基本字符集中的字符。大多数机器的字节由8比特构成,字则由32或64比特构成,也就是4或8字节。
对于浮点数的类型和精度,有:
类型 | 字数(位数) | 有效数字 |
---|---|---|
float |
1(32) | 7 |
double |
2(64) | 16 |
long double |
3或4(96或128) | 硬件的实现不同,精度各不相同 |
字符型被分为了三种:char
、signed char
和unsigned char
。和int
不同,类型char
和类型signed char
并不一样。字符型有三种,但是字符的表现形式却只有两种:带符号的和无符号的。所以char
实际上会表现为signed char
和unsigned char
形式中的一种,具体是哪种由编译器决定。
unsigned char c=-1; // 假设char占8比特,c的值为255
,因为unsigned char
可以表示0到255共256个数字,所以最后的值是-1对256取模为255。
signed char c2=256; // 假设char占8比特,c2的值是未定义的
,当我们赋给带符号类型一个超出它表示范围的值时,结果是未定义的。此时,程序可能继续工作,可能崩溃,也可能产生垃圾数据。
int
与unsigned int
相加的时候是int
变成unsigned int
的形式,然后再运算,即先进性类型转换。如果这里有int
类型的-1和unsigned int
类型的1相乘,结果本来希望是1,最后的结果为unsigned int
的最大值。
for(unsigned u = 10; u >= 0; u--)
会死循环,0之后是unsigned int
的最大值。解决方法:
while(u > 0)u++;
while(u > 0) {
u--;
}
某些一望而知类型的常量叫做字面值常量,比如1.0、21、024、0x14。
整型字面值具体的数据类型由它的值和符号决定。默认情况下,十进制字面值是带符号数;八进制和十六进制字面值既可能是带符号的也可能是无符号的。十进制字面值的类型是int
、long
和long long
中能容纳当前值中尺寸最小的那个;八进制和十六进制字面值的类型是能容纳其数值的int
、unsigned int
、long
、unsigned long
、long long
、unsigned long long
中的尺寸最小者。但如果均放不下,则会产生错误。
十进制字面值不会是负数,比如-21是一个负十进制字面值,那个负号并不在字面值之内,它的作用仅仅是对字面值取负值而已。
如果两个字符串字面值位置紧邻且仅由空格、缩进和换行符分割,则它们实际上是一个整体。当书写的字符串字面值比较长,写在同一行里不太合适时,就可以采取分开书写的方式。比如:
cout << "abc "
"def" << endl;
通过添加前缀和后缀,可以改变整型、浮点型和字符型字面值的默认类型:
L
作为前缀可表示宽字符型字面值,类型为wchar_t
,比如:L'a'
。u/U
作为前缀可表示Unicode 16/32
字符型字面值,类型为char16_t/char32_t
。u8
作为前缀可表示UTF-8
字符串型字面值,类型为char
。
浮点型字面值中f
或F
作为后缀时表示的类型为float
,l
或L
作为后缀时,类型为long double
。
第二节 变量
对象是指一块能存储数据并具有某种类型的内存空间。
初始化不是赋值的一种!!!在C++
中,初始化和赋值是两个完全不同的操作。
列表初始化:定义一个名为a
的int
变量并初始化到0,以下的4条语句都可以做到这一点:
int a = 0;
int a = {0};
int a{0};
int a(0);
这是c++11
新标准的一部分,用花括号来初始化变量的形式被称为列表初始化,无论是初始化对象还是为对象赋新值,都可以使用这样一组由花括号括起来的初始值。
这几句的区别:
- 初始化四句都可以,但是赋值操作时,
a(0)
和a{0}
不合法,只能是a = (0)
或a = {0}
。 ()
不会检查类型的问题,可以强制类型转换,但是{}
会判断类型是否正确,如果不正确直接报错。
为了允许把程序拆分成多个逻辑部分来编写,C++
语言支持分离式编译(separate compilation)
机制,该机制允许将程序分割为若干个文件,每个文件可被独立编译。这需要有文件间共享代码的方法,一个代码可能需要使用另一个文件中定义的变量。一个实际的例子是cin
和cout
,定义于标准库,却能被我们写的程序使用。
为了支持分离式编译,C++
语言将声明和定义区分开来。声明(declaration)
使得名字被程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义(definition)
负责创建与名字关联的实体。即变量可以杯声明多次,但只能被定义一次。
声明变量但并不定义变量,使用extern
,而且此时不能显式地初始化变量,因为显式初始化的声明会变成定义,即extern double pi = 3.1415926
相当于让extern
失效 。
C++
是一种静态类型语言,其含义是在编译阶段检查类型。在C++
语言中,编译器负责检查数据类型是否支持要执行的运算。
变量命名规范:
- 变量名一般用小写字母
- 用户自定义的类名一般以大写字母开头,比如
Sales_item
- 如果标识符由多个单词组成,则单词间应有明显区分,即用下划线分开或下一个单词首字母大写
现在有一个全局变量b
,main
函数中有一个局部变量b
,那么还怎么访问那个全局的b
呢?用::b
,我们说过::
是作用域操作符,因为全局作用域本身并没有名字,所以左侧为空时,向全局作用域发出请求获取作用域操作符右侧名字对应的变量。
第三节 复合类型
我们平时说的引用指的是左值引用。
引用就是起了个别名。
引用必须被初始化,因为和它的初始值对象要一直绑定在一起,没有自己的内存,所以引用并非对象。
比如b
是a
的引用,c
也是引用,可以用int &c = a;
,也可以用int &c = b;
。
10这种常量得用const int &
,不能用int &
。00
引用与实际对象的类型必须得一样。
指针和引用的不同点:
- 指针本身也是一个对象,允许对指针赋值和拷贝。
- 指针的生命周期里可以指向不同的对象,引用不能变。
- 指针无需在定义时赋初值,如果没有被初始化,会有个不确定的值。
赋值空指针的方法:
int *p1 = nullptr; // C++11新标准引入的特殊类型字面值,可以被转成任意其他的指针类型
int *p2 = 0;
int *p3 = NULL; // 需要先#include cstdlib
预处理器是运行于编译过程之前的一段程序,预处理变量不属于命名空间std
,由预处理器负责管理,因此可以直接使用预处理变量而无需在前面加std::
。
新标准下最好使用nullptr
,不尽量避免使用NULL
,NULL
是老代码中用的,并且预处理程序直接把它变成0了。
指针是否相等看的是地址是否相等,而不是指的是不是一个东西,比如:
int a[3][4];
int *p1 = a[0];
int *p2 = &a[0][0];
cout << (p1 == p2); // 1
void *
虽然可以接收任意类型的指针,但是操作有限,比如可以进行地址比较、作为函数的输入或输出、赋值给另一个void *
,但是不能直接操作该对象,因为不知道类型,也就不知道可以怎么操作,在void *
的视角来看,内存空间仅仅只是内存空间。
int* p1, p2
可以这么写,但是会产生误导,因为这里的意思是p1
是int *
,p2
是int
。
指向指针的指针:
int ival = 1024;
int *pi = &ival;
int **ppi = π
cout << **ppi << endl; // 1024
ppi
存的是pi
的地址,pi
存的是ival
的地址。
因为引用不是一个对象,所以不存在引用的指针;但是指针是对象,所以存在指针的引用:
int i = 24, *p = &i;
int *&r = p; // 注意次序,离变量越近的东西对变量的影响越大,所以是个引用,然后是指针的引用,最后是一个int类型指针的引用。
cout << *r << endl; // 24
拓展:
int i = 24, *p = &i;
int j = 42;
int *&r = p;
cout << *r << endl; // 24
p = &j;
cout << *r << endl; // 42(跟着p走)
和:
int i = 24, *p = &i;
int j = 42;
int* const &r = &i; // 这个是常量,所以要在*和&之间加个const,然后r和i的地址锁死
cout << *r << endl; // 24
p = &j;
cout << *r << endl; // 24,p动不影响r的值。
第四节 const限定符
const
限定的变量不是说改变值会报错,是赋值操作就会报错,值和原值一样也会报错。注意:全局变量也不可以省略初始化而认为它是0,也会报错。
与非const
限定变量相比,const
能完成大部分操作,比如强制类型转换。
变量之间互相初始化,和它们是不是const
无关,都不会报错,因为只传递值,一旦拷贝完成,二者便没有什么关系。
编译器在编译过程中把用到const
变量的地方都替换成其对应的值。
默认情况下,const
对象仅在文件内有效。我们如果希望只有一个该变量,剩下的文件都使用这一个const
变量。解决方法是不管是声明还是定义都添加extern
关键字,这样只需要定义一次就行了:
// a.cc文件中定义并初始化一个常量,该常量能被其他文件访问,需要用extern!!!
extern const int bufsize = func();
// a.h头文件中声明,和a.cc中的是同一个
extern const int bufsize;
常量的引用:const int &r1 = c1;
,引用及其对应的对象都是常量。不能用非const
类型引用指向const
对象。因为不能通过引用来改变原const
对象的值。反过来可以,因为改变int
对象的值而使const
引用的值改变无所谓,引用本身也不是对象。
Q:一个常量引用被绑在一个对象身上发生了什么呢?看下面的一串代码:
double dval = 3.14;
const int &ri = dval;
cout << ri << endl; // 3
为了确保const int
引用绑定的是个const int
,编译器搞了个临时变量temp
出来:
const int temp = dval; // temp = 3
const int &ri = temp; // ri绑定临时常量
对于const
来说,指针的规则和引用一样,不能让普通的int
指针指向const int
常量。
int *const
是常量指针(从右往左读),指的是从一而终都指向这一个东西。
我们定义让自己是常量的const
叫做顶层const
,让指向对象为常量的const
为底层const
。
常量表达式:值不会改变且在编译过程中就能得到计算结果的表达式。
一个对象是不是常量表达式由它的数据类型和初始值共同决定,例如:
const int max_files = 20; // max_files是常量表达式
const int limit = max_files + 1; // limit是常量表达式,因为max_files被20替代,所以直接可以知道是21
int staff_size = 27; // 不是const,所以不是常量表达式
const int sz = get_size(); // 因为不是编译过程能计算出来的,所以不是常量表达式
C++11
新标准规定,允许将变量声明为constexpr
类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr
的变量一定是一个常量,而且必须用常量表达式初始化。对于constexpr int sz = size();
,只有size
是一个constexpr
函数时,才是一条正确声明语句,而新标准允许定义一种特殊的constexpr
函数,这种函数足够简单以使得编译时就可以计算其结果,这样就能用constexpr
函数去初始化constexpr
变量了。(见第六章)
总结:一般来说,如果认定变量是一个常量表达式,那就把它声明成constexpr
类型。
算术类型、引用和指针都属于字面值类型,可以定义成constexpr
类型。指针和引用虽然可以这样被定义,但是初始值受到严格限制,一个constexpr
指针的初始值必须是nullptr
或者0,或者是存储于某个固定地址中的对象。
函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr
指针不能指向这样的变量,定义于所有函数体之外的对象其地址固定不变,能用来初始化constexpr
指针。(还真每次都不变)
但是constexpr
指针指的是自己是常量,而不是所指对象为常量:constexpr int *q = nullptr; // q是一个指向整数的常量指针
。即constexpr
不管是不是指针都是顶层const
。
constexpr int i = 42; // i是整型常量,和const的区别是会检查右侧是不是常量表达式
int j = 21;
constexpr const int *p = &i; // 常量指针指向常量
constexpr int *p1 = &j; // 可以指向非const
第五节 处理类型
复习:
typedef double wages;
typedef wages base, *p; // base = double, p = double*
还可以用using
,即别名声明来定义类型的别名:using SI = Sales_item;
,就可以用SI
代替Sales_item
类型了。
注意,不是说typedef
就是直接替换的意思!!!:
typedef char *pstr;
const pstr cstr = 0; // 注意这里相当于:char *const ctr = 0; 即cstr为常量指针而非指针常量,直接换就错了
const pstr *ps; // char **const ps; 即*挪到前面去
C++11
新标注引入了auto
类型说明符,可以让编译器替我们分析表达式所属的类型。auto
让编译器通过初始值来推断变量的类型,显然auto
定义的变量必须有初始值。
可以理解为auto
换成一个词,这个词是类型。所以有:
auto i = 0, *p = &i; // 正确,i为int,p为int *。
auto sz = 0, pi = 3.14; // 错误,sz为int,pi为double。
auto
初始化的右侧为引用的情况下,类型为本身变量对象的类型,而不是也是引用。
auto
忽略掉顶层的const
,而保留底层的const
。
const int ci = 5;
auto e = &ci; // e是一个指向整数常量的指针(因为对常量对象取址就是让这个所指对象是常量,所以是底层const,应该保留,所以e是const int*。而如果是普通的int,这里就是int*。
如果auto
前还有const
,这个const
为顶层的(很显然因为顶层被忽略,所以这里是补的顶层的const
)。
而如果要让auto
带上引用的话,可以用auto &
。比如const auto &j = 42;
,但如果auto &j = 42;
就错了,会识别为int &j
,于是报错。但是:
const int ci = 10;
auto &g = ci; // 这个g就被识别为const int &,因为这个const同样是说被指向的对象是常量,所以为底层的const
希望从表达式的类型中推断出要定义的变量的类型,但是不想用该表达式的值初始化变量。C++11
引decltype
,它的作用是选择并返回操作数的数据类型,并不实际计算表达式的值。比如:decltype(f() + 1) sum = x;
规定sum
的类型,甚至不用调用f()
。
decltype
处理顶层const
和auto
不同,decltype
不会忽略const
也不会忽略引用。引用从来都作为其所指对象的同义词出现,只有用在decltype
处是一个例外。
decltype(*p)
的结果类型是int &
。
decltype
中加括号是一种操作符,相当于取址,即可得到引用类型:decltype((i)) d;
是错误的,因为为int &
必须初始化。decltype((variable))
一定是引用,而decltype(variable)
只有当variable
是引用的时候才是引用。
第六节 自定义数据结构
一般来说,最好不要把对象的定义和类的定义放在一起,一会定义类,一会定义变量,是一种不被建议的行为。
确保头文件多次包含仍能安全工作的常用技术是预处理器。之前涉及到的预处理功能为#include
,当预处理器看到#include
标记时会用指定的头文件的内容代替#include
。
C++
程序还会用到的一项预处理功能是头文件保护符,头文件保护符依赖于预处理变量。预处理变量有两种状态:已定义和未定义。#ifdef
当且仅当变量已定义为真,#ifndef
当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直至遇到#endif
指令为止。
为了避免与程序中其他实体发生名字冲突,一般把预处理变量的名字全部大写。
标签:const,常量,int,5th,C++,类型,primer,指针,变量 From: https://www.cnblogs.com/fansoflight/p/17149907.html