C++Primer第五版学习笔记 第二章
第二章 变量和基本类型
基本类型
基本类型 | 含义 | 最小尺寸 | 备注 |
---|---|---|---|
bool | 布尔类型 | 未定义 | |
char | 字符 | 8位 | |
wchar_t | 宽字符 | 16位 | |
char16_t | Unicode字符 | 16位 | |
char32_t | Unicode字符 | 32位 | |
short | 短整型 | 16位 | |
int | 整形 | 16位 | |
long | 长整型 | 32位 | |
long long | 长整型 | 64位 | |
float | 单精度浮点数 | 6位有效数字 | |
double | 双精度浮点数 | 10位有效数字 | |
long double | 扩展精度浮点数 | 10位有效数字 | |
关于无符号数据:
- 出去布尔型和扩展的字符型外,其他类型可以通过添加unsigned表示无符号类型。但是与整形不同,字符型被分为三种,char, unsigned char, signed char。 char 与 signed char不同!
- 运算中不要混用有符号和无符号的类型,如果带符号类型取值为负时会出现异常结果,这是因为带符号数为自动转化为无符号数。
关于选择类型:
- 当明确不会有负值时,选择无符号类型
- int类型和long类型有一样的尺寸,所以当数据长度超过int,选择long long 类型
- 算术运算符中不要使用char和bool,如果需要使用一个不大的整数,明确时signed char 或 unsigned char
- 当需要进行浮点数运算时,使用double而不是float, 精度提升但是计算量却相差无几
进制
- 20 十进制
- 020 八进制
- 0x20 十六进制
字符串字面值
- 字符串一般以’\0’作为结束符,所以一个字符串的长度为其字面值长度加上1。
- 字符’a’的长度为1, 字符串"a"的长度为2
- 除了定义变量的类型意外,也可以直接指定字面值的类型,如’ 42LL ’ 定义了long long 类型的数字42, 此类定义分为前缀和后缀;
字符(串)前缀 | 含义 | 类型 |
---|---|---|
u | Unicode 16 字符 | char16_t |
U | Unicode 32 字符 | char32_t |
L | 宽字符 | wchar_t |
u8 | UTF-8 | char |
整形后缀 | 含义 |
---|---|
u or U | unsigned |
l or L | long |
ll or LL | long long |
浮点型后缀 | 含义 |
---|---|
f or F | float |
l or L | long double |
- 显然我们可以混用,如U和L结合为unsigned long
转义序列
转义序列 | 含义 |
---|---|
\n | 换行符 |
\v | 纵向制表符 |
\\ | 反斜线 |
\r | 回车符 |
\t | 横向制表符 |
\b | 退格符 |
? | 问号 |
\f | 进纸符 |
\a | 报警(响铃)符 |
\" | 双引号 |
\’ | 单引号 |
初始化与赋值
- 初始化是创建变量时赋予一个初始值,且对于列表初始化而言,当使用内置类型的变量时,如果使用列表初始化并且当初始值存在丢失信息的风险时,编译器会报错。(如 int x = {1.2}),其他方法会直接舍弃部分值。
// 初始化的形式
int x = 0
int x(0)
//列表初始化
int x = {0}
int x{0}
- 赋值是把对象当前值擦除,而以一个新的值来代替
默认初始化
- 内置对象的默认初始化:全局变量接受默认初始化的值,如int 默认初始化为 0,局部变量(包括main())不被初始化,也无法引用。
- 类的默认初始化则由类自己决定,绝大多数类接受无参数的默认初始化,如std::string str初始化为空串。
- 建议显式初始化每一个内置类型的变量。
变量的声明和定义
- 为了支持分离式编译,C++支持声明和定义分离开,如果要对 一个变量进行多次引用,则必须只能再一个文件中定义,其他文件只能声明不能定义。
extern int x; //声明
extern int x = 1; //定义,变量只能被定义一次,但可以多次声明。
int x; //声明并定义
变量命名规范
- 由字母,数字,下划线组成,且只能由下划线和字母开头
- 用户定义的标识符不能出现连续两个下划线
- 开头不能以下划线加大写字母开头
- 定义在函数体外的标识符不能以下划线开头
- 类名一般用大写字母
- 变量名一般用小写字母
- 需要体现实际含义,并且如果由多个单词构成,需要用下划线隔开
当局部变量与全局变量同名
//应尽量避免此类情况发生。
int val = 1
int main(){
cout<< val; //1
int val = 2;
cout<< val; //2
cout<< ::val; //1
return 0;
}
引用
int x = 1;
int &y = x; //引用只是为原来的变量起了第二个名字
double &d = x; //错误,引用需要绑定用一种类型
double &d = 1.2; // 错误,不能对字面值使用。
double &d; //错误,必须初始化
###指针
- 指针和引用都是符合类型,但是指针与引用也有不同,如指针本身就是一个对象,一般存放指向的对象的地址,还有指针可以不用初始化,应用必须初始化。。
int x = 1;
int *p = &x, *p2;
p2 = p; //p2指向x
double *d = x; //错误,类型不同。
cout << p << *p+1; //地址和2
使用*p来使用x的值称为解引用操作,其只适用于那些已经确定了指向对象的指针 。
//定义空指针的方法
int *x = nullptr; //C++11(最好使用nullptr)
int *x = 0; //需要#include <cstdlib###
int *x = NULL;
int d = 0;
int *x = d;// 错误!!!
-
使用未初始化的指针容易引发错误,所以定义指针时尽量初始化为nullptr或者指向某个对象
-
此外,指针可以用于是非判断,所有非0指针的值都是True, 但是如果是无效指针可能产生无法预计的后果,因此如果需要判断指针,可以将其置于try结构中去。
-
void *p指针是一种特殊的指针,可以存放任意对象的地址,但是我们无法得知该地址的对象的类型,因此只能存放地址,进行比较等而不能操作指针指向的对象。
int x = 1;
void *p = x;
cout<< *p; //错误
- 需要注意的是,定义指针的*只对紧随其后的第一个对象有意义。
int *p; //首选
int* p;
/* 前两种定义完全相同,但是第二种会引起歧义,int*并不是一种全新的类,不能连续定义*/
int* p1, p2;
//这里的p1是指向int的指针,p2是int。
- 指向指针的引用。
int *p;
int *&r = &p;
*r == *p; //True
const常量
-
const常量只能在初始化的时候赋值,所以const常量也必须初始化!
-
默认情况下const常量只在文件内容内生效,如果有多个文件需要使用,则需要在一个文件中定义,在其他文件中声明,其中声明与定义全部加上extern。
-
对于const的引用必须使用const 类型 对象的形式,需要注意的是,对const的引用可能是一个非const的变量,如下面的y,y可以修改,但是无法通过z去修改y的值。
int y = 1;
const int x = 2;
const int &r = x;
const int &z = y;
- 与常量引用一样,指向常量的指针也没有规定其所指的对象必须是一个常量。所谓指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定该值不能被其他途径改变。反之,如果指针是常量,并不意味着不能通过指针修改常量的值,只是不能修改指针。
int x = 0;
int const* p1 = &x; // p1可以指向int型,但是不能指向常量,必须初始化且不可修改
const int *p2 = &x; // p2指向常量整数,必须初始化,但是p2本身所指向的对象可以修改
- 顶层和底层const,顶层const可以表示任意类型的对象是常量,底层一般与复合类型有关,如引用和指针,其中,指针既可以是顶层const,也可以是底层const。
const int x = 1; //顶层
int const* p = &x; //顶层
const int *p = &x; //底层
constexpr
- 声明为constexpr的变量由编译器来验证变量的值是否是一个常量表达式,声明为constexpr的变量一定是一个常量,且必须用常量表达式初始化。
constexpr int x = 20;
constexpr int y = x + 1; //x+1是一个常量表达式
constexpr int z = size(); //此处,只有当size()是一个constexpr函数时才是一条正确的说明语句。
- 尽管指针和引用都能定义成constexpr,但它们的初始值却受到严格限制,一个constexpr指针的初始值必须是nullptr或0,或是储存于固定地址的某个对象。
- 此外,在constexpr声明了一个指针,限定符constexpr仅对指针生效,与指针所指的对象无关。
const int *p = nullptr; //指向常量int的指针
int const *p = nullptr; //指向int的常量指针
constexpr int *q = nullptr; //指向int的常量指针,顶层const
使用类型别名 typedef或using(C++11)
typedef double base;
typedef double *double_p;
// base 是double的同义词,double_p是double*的同义词。
using SI = Sales_Item;
// SI是Sales_Item的同义词。
类型指示符
- 使用auto可以让编译器根据初始值自动判断类型,所以auto类型必须初始化。且由于一条auto语句只能有一个基本数据类型,所以如果声明多个变量时,所有变量需要是一个基本类型。
const int x;
auto &m = x, *p = &x; //m是对整形常量的引用,p是对x的指针。
// 由于&和*都只属于某个声明符,而非基本数据类型的一部分,所以上面的auto初始化都是const int,
可以通过。
- 使用auto定义变量时,编译器一开始推断的类型可能不准确,编译器会适当的改变结果类型使其输出更符合初始化规则。
- 使用引用时:
int i = 0;
int &r = i;
auto x = r;
- auto 一般会忽略顶层const,同时底层const会保留下来,比如当初始值是一个指向常量的指针
const int ci = i; &cr = ci;
auto b = ci; //b是一个整数,因为ci的推演类型是一个int
auto c = cr; //c是一个整数,同上
auto d = &i; //d是一个整形指针
auto e = &ci; //e是一个指向整数常量的指针
const auto f = ci; //f是一个常量整数
- 也可以对引用的初始化使用auto。
auto &r1 = ci;
auto &r2 = 42; //错误,只有引用常量可以绑定字面值。
const auto &r3 = 42;
- C++11提供了第二种类型指示符decltype,它的作用是选择并返回操作数的数据类型。在此过程中,编译器不会实际计算表达式的值,只分析其返回值的类型。
decltype(fn()) sum = x; //sum的类型就是fn的返回值类型
/*decltype处理顶层const和底层const,不会追究,只会返回当前变量的类型*/
int x = 1, &r = x, *p = &x;
decltype(x) y = x; //y = 1
decltype(r) z = x; //z = r,是x的引用,所以此处必须初始化。
decltype(r+0) m; //虽然r是引用,但是r+0返回的是整数,所以m是int。
decltype(*p); //如果表达式的内容是解引用操作,则decltype将得到引用类型!!!
- 需要注意的是,对于decltype而言,除了自带的括号decltype()之外,对于没有括号的变量,得到的就是该变量的类型,对于加了括号的变量,编译器会把它当成一个表达式,由于变量是一种可以作为赋值语句的特殊表达式,所以这样的decltype就会得到引用类型。
int i = 1;
decltype((i)) d = i; //双层括号的结果永远是引用int&
decltype(i = i) d =i; //赋值会产生引用,所以d是引用int&
- auto 和 decltype的区别
- auto用编译器计算变量的初始值来推断其类型,
decltype只分析表达式不计算值。 - auto会忽略顶层const,
decltype会保留顶层const。 - decltype对于表达式加上括号返回引用,auto则不同。
int a = 3;
auto c1 = a; //int
decltype(a) c2 = a; //int
decltype((a)) c3 = a; //int&
const int d = 5;
auto f1 = d; //int
decltype(d) f2 = d; //const int
结构体/类
- 定义结构体的时候不建议同时定义变量名。
- 初始化结构体的变量时,类内初始值将用于初始化数据成员,没有初始值的成员将被默认初始化。
头文件.h
- 为了确保各个文件中的类的定义一样,类通常被定义在头文件中,而且类所在的头文件的名字应该与类的名字一样。
- 头文件一般只包含只能定义一次的实体,如const和constexpr变量。
- 头文件也经常包含其他头文件的内容。
头文件保护符
-在编译的过程中,每一个.cpp文件被看成一个单独的文件来编译成单独的编译单元,#ifndef 保证类的头文件在同一个.cpp文件中被多次引用后不会出现重定义问题。
- ifdef 当且仅当变量已定义为真,一旦检验结果为真,执行后续操作直到endif
- ifndef 当且仅当变量未定义为真,一旦检验结果为真,执行后续操作直到endif
#ifndef SALE_DATA_H //该变量未定义
#define SALE_DATA_H //定义该变量,且该变量不受作用域限制,之后每次调用此头文件,都会在上一个判定中判定负,从而避免重定义。
#include<string>
struct Sale_data{
//
};
#endif