一、基本类型(内置)
整型 | 整型 | 整型 | short |
---|---|---|---|
int | |||
long | |||
long long | |||
字符 | char | ||
wchar_t | |||
char16_t | |||
char32_t | |||
布尔值 | bool | ||
浮点型 | float | ||
double | |||
long double | |||
void类型 |
C++标准规定的各类型最小尺寸:
short <= int <= long <= long long
类型 | 最小尺寸 | 含义 |
---|---|---|
bool | 未定义 | 布尔类型 |
char | 8bit | 字符 |
wchar | 16bit | 宽字符 |
char16_t | 16bit | Unicode字符 |
char32_t | 32bit | Unicode字符 |
short | 16bit | 短整型 |
int | 16bit | 整型l |
long | 32bit | 长整型 |
long long C++11定义 |
64bit | 长整型 |
float | 6位有效数字 | 单精度浮点数 |
double | 10位有效数字 | 双精度浮点数 |
long double | 10位有效数字 | 扩展精度浮点数 |
内置类型机器实现
可寻址的最小内存块称为“字节(byte)”,存储的基本单元称为“字(word)”。
以下是字节为8bit,字为32bit的机器上一个字的内存区域:
- 736424为这个字的地址,736425为这个字的第2个字节的地址。
- 这个字的内存如何被解释,取决于它的数据类型。
带符号和无符号
signed int a; // 有符号,signed + 类型
unsigned int a; // 无符号,unsigned + 类型
char a; // 一般解释为signed char
float a; // double的性能float和差不多,但精度更高。
double a;
// 关于有符号类型的取值范围,C++并为规定,只规定了正负数必须平衡。
signed char a; // 取值范围:-128 ~ 127
unsigned char a; // 取值范围:0 ~ 255
int a; // -2147483648 ~ -2147483647
unsigned int a; // 0 ~ 4294967295
类型转换
算术->bool,0为false,其他为true。
bool->算术,false为0,true为1。
浮点->整型,保留整数部分。
整型->浮点,小数部分标记为0,如果整型空间大于浮点,损失精度。
无符号超出范围,模除取余。
带符号超出范围,结果不可以预知。
要避免不可预知和依赖于环境的行为,如把int看成就是4byte,这样的程序是不可移植的。
// 无符号尽量避免用于循环。
for (unsigned u = 10; u >= 0 ; --u ) //死循环
std :: cout << u << std: : endl ;
// 有符号和无符号不要混用
int a = 1, b = -1;
unsigned int c = 1;
cout << a * b << endl; // -1
cout << a * c << endl; // 42亿多
字面值常量literal
整型
十进制、八进制(0开头)、十六进制(0x、0X开头)。
十进制数的类型是有符号的int~long long之间最小的能装下它的,都不能装下,报错。
八、十六进制的类型是无论有符号的int~long long之间最小的能装下它的,都不能装下,报错。
浮点型
3.141593 .14159E0 0. 0e0 .001
字符(串)
‘ a ‘ :字符字面值
“ Hello World!”:字符串字面值,实质是\0结尾的字符数组。
转义序列
\ + 1~3为八进制数字,最多能转到3位,超过的另算,如\1234是\123和4两个字符。
\x + 任意位十六进制数字,根据(扩展)字符集转义结果,可能会转义不出来报错。
换行符 \n(\12) | 横向制表符 \t |
---|---|
纵向制表符 \v | 退格符 \b |
反斜线 \\ | 问号 \? |
回车符 \r | 进纸符 \f |
报警( 响铃)符 \a(\7) | 双引号 \“ |
\40 (空格) | 单引号 \‘ |
指定字面值
整型字面值 | 浮点型 | ||
---|---|---|---|
后缀 | 最小匹配 | 后缀 | 最小匹配 |
u 或 U | unsigned | f 或 F | float |
l 或者 L | long | l 或 L | long double |
ll 或 LL | long long | ||
字符(串)字面值 | |||
前缀 | 含义 | 类型 | |
u | Unicode16字符 | char16_t | |
U | Unicode32字符 | char32_t | |
L | 宽字符 | wchar_t | |
u8 | utf8仅限字符串 | char |
二、变量
有名字,有类型的一块内存空间。类型决定内存空间大小等。
列表初始化、默认初始化
用花括号初始化叫列表初始化,如果存在信息丢失,会编译报错,多使用这种。
内置类型定义在函数外部默认初始化为0,否则是未初始化。在函数外部的变量其实在编译阶段已经在操作它了,也就是说程序加载到内存的时候,它就已经在内存当中了;而函数内部的变量,只有在执行函数压栈了才会加载到内存,这两个是完全不一样的生命周期。
int a = 1, b = 2; // 正确
int a(0); // 正确
int a, b = 1, 2; // 编译报错
int a, b = 1; // a未初始化,b初始化1
int units_sold = {0}; // 列表初始化
int units_sold{0} ; // 列表初始化
long double ld = 3.1415926536;
int a{ld}, b = {ld} ; // 编译报错:转换未执行,因为存在丢失信息的危险
int c(ld), d = ld; // 正确: 转换执行,且确实丢失了部分值
声明、定义
基本数据类型 声明符, 声明符,…声明符;
声明:我要使用一个来自外部的变量。这样编译器就会知道你要用的这个变量来自文件外。它就会去其他文件查找,然后把你们“链接”起来,你就能通过这个变量访问到它的内存地址了。
定义:我要创建一个自己的变量。编译器就会知道这个变量是你这里的,别人如果要用,编译器也能从你这里找到。
最终的目的就是,编译器可以单个编译程序文件(程序代码),通过“链接”组装起来就称为一个(程序)可执行文件。
extern int i; // 声明i 而非定义i
int j; // 声明并定义j
extern int i = 1; // 定义i,而非声明i
int fuck(){
extern int i = 1;
// 编译器:“你特么到底几个意思,既然extern声明,你就干脆在函数外进行
// 就算你是声明,你初始化是干啥,到底是声明还是定义
// 我不干了,我报错”
}
C++是静态类型语言, 即使在编译阶段确定变量的类型(类型检查),类型决定了一个变量的内存“含义”。
名字(标识符)
变量(类、对象)、函数、类型都可以有自己的名字。
C++内部用了的叫保留字(关键字)。
int __a = 1; // 错误:不要两个下划线开头
int _A = 1; // 错误:不要下划线接大写字母开头
int _a = 1; // 错误:外部变量不要下划线卡头
作用域
- 全局作用域
- 局部作用域
- 语句作用域
- 类作用域
- 命名空间作用域
- 文件作用域
一块“限制影响”的区域,比如你在当前作用域做了一些动作(定义变量、函数、类型),那么这些动作的影响范围就是当前位置~作用域结束的位置。
块作用域:{}花括号括起来的。
全局作用域:块作用域之外的。
作用域可以嵌套,分内层作用域、外层作用域。
#include <iostream>
int main () { // 名字main表示一个函数,在全局作用域,所有文件都可以使用
// 全局作用域:花括号之外
{ // 块作用域:我们设为a
int sum = 0; // 局部变量sum,只能在作用域a中存在。
for (int val = l ; val <= 10 ; ++val)
sum += val;
std::cout << "Sum of 1 to 10 inclusive is" << sum << std: : endl;
} // 作用域a结束
return 0;
}
三、复合类型(引用、指针)
引用
变量的另外一个名字,第一个名字是定义的时候,定义时必须初始化。
左值引用、右值引用。
int i1 = 1, i2 = 2;
int &ref1 = i1, &ref2 = i2; // 引用
int &ref1, &ref2; // 错误,必须初始化
ref1 = 2; // i1 = 2
int i3 = ref; // i3 = 1
指针
是一个变量(一块内存),存储指定类型对象的内存地址值。定义时,请初始化为nullptr或者其他合法地址。
void*指针,保存任何类型对象的内存地址,无法解引用,因为不知道如何解释内存值。
int i = 1, *p1, *p2;
p1 = &i; // p1的值 = i变量的内存地址。
int* p3 = &i; // 定义并初始化p3,注意要用取地址符(&)
double d = 1.0;
int zero = 0;
int* p1 = &d; // 错误,类型必须严格匹配。
double* pd = zero; // 错误,类型不匹配,虽然是0。
int* p1, p2; // 避免这种定义声明方式,容易歧义
int* p1, *p2; // 指针正确的定义声明方式。
cout << &p1 << endl; // 解引用符(*)
int *p = 0; // 指向内存地址0(一般都是不可访问地址),所以解引用将报错。
int *p = NULL; // 预处理变量,等价于int *p = 0;
int *p = nullptr; // C++11引入,真正的空指针,不指向任何内存,代替NULL和0
int **p2p = nullptr; // 指针的指针,和普通指针一样,只不过存储的是指针地址。
p2p = &p;
*p2p; // 指针p的值
**p2p; // 指针p解引用,也就是*p
int *&ref_p = p; // 指针的引用
// 从右往左阅读
// 对于复杂的语句,我们别忘记了用从右往左阅读的习惯。比如下面:
// 1、ref_p2p是一个引用
// 2、引用的对象是一个指针的指针
// 所以紧挨名字右边的符号决定了类型。
int **&ref_p2p = p2p; // 指针的指针的引用
const(read only)
const限定符,声明对一个对象的操作方式是只读模式。
int i = 42;
const int ci = i; // const定义声明。
int j = ci; // 可以进行非修改变量的操作。
const int &const_ref_ci = ci; // 正确
int& ref_ci = ci; // 编译错误,必须const修饰引用。
/******多文件共享const对象**********/
// file1.h
extern const int MOTHER_FUCKER; // 声明MOTHER_FUCKER会在其他文件用到。
// file2.cc
extern const int MOTHER_FUCKER = 1; // 定义
int i = 42 ;
const int r1 = i; // 允许将 r1 绑定到普通对象上
const int &r2 = 42; // 正确 : r1 是一个常量引用
const int &r3 = r1 * 2 ; // 正确 : r3 是一个常量引用
int &r4 = r1 * 2; // 错误 : r4 是一个普通的非常量引用
const double pi = 3.14;
double *ptr = &pi ; // 错误: ptr是一个普通指针
const double *cptr = &pi ; // cptr可以指向一个双精度常量
*cptr = 42; // 错误: 不能给*cptr 赋值
double dval = 1.0;
cptr = &dval; // 正确。以只读模式来理解就顺了。
int errNumb = O;
int *const curErr = &errNumb ; // curErr将一直指向errNumb
const double pi = 3.14159;
const double *const pip = π // pip是一个指向常量对象的常量指针
constexpr
常量表达式满足两个条件:
- 编译阶段计算出结果
- 不会改变
constexpr的需求场景,很显然在一个复杂的系统中,常量是经常被用到的,但是并没有很好的办法可以保证一个表达式是一个常量表达式。通过constexpr显式声明为常量表达式,编译器就会在编译阶段检查该表达式是否是常量表达式,否则编译错误。
const int max_files = 20; // 常量表达式
const int limit = max_files + 1; // 常量表达式
int staff_size = 27; // 编译错误,不是常量表达式,可修改。
const int size = get_size(); // 编译错误,get_size()不是constexpr函数
constexpr int expr = 1; //
constexpr int limit = expr + l ; // 正确
constexpr int sz = size(); // size()如果是constexpr函数,则编译通过。
// 注意下面const和constexpr的区别
const int *p = nullptr; // p的类型是const int*
constexpr int *q = nullptr ; // q的类型是int* const
constexpr声明时用的类型必须是很简单的,类型可以是下面这些:
- 字面值类型:
- 算术类型
- 引用
- 指针
- nullptr
- 0、NULL
- 全部变量指针
- 字面值常量类
- 数据成员都是字面值类型的聚合类
四、处理类型
typedef
类型也可以有别名。传统方法typedef,新标准using。
/*********************语法**************************/
// alter1, alter2,...,alter3 和 TypeName 是同义词
typedef TypeName alter1, alter2,...,alter3
/*********************例子**************************/
typedef double wages; // wages是double的同义词
typedef wages base,*p; // base是double的同义词, p是double*的同义词
typedef char *pstring; //pstring是char*,要把char*看成一个整体的不可分割的类型了。
const pstring str = "asdfasdf"; // 等价
char *const str = "asdfasdf"; // 等价
const char* str = "asdfasdf"; // 不等价,不能简单的替换来看。
using
头文件最好不要使用using,因为头文件会被多次包含,这样容易造成名字冲突。
// 意义:使用命名空间name里定义的所有名字。
// name: 命名空间,如std、std::placeholders
// 尽量避免使用,造成命名空间污染。
using namespace name;
//例如:
using namespace std;
// 意义:使用某个命名空间的名字name,无需再显式加上前面的命名空间
// 如:using std::placeholders::_1,使用std下的placeholders下的_1名字
// 推荐使用,要啥就显示指定。
using namespace_1::...::namespace_n::name;
//例如:
using std::cin;
using std::cout;
// 意义:newname是oldname的同义词
// newname: 新的名字
// oldname: “旧”的名字:可能是名字,类名,namespace等。
using newname = oldname;
//例如:
using SI = Sales_item; //SI是Sales item的同义词
auto
通过初始值来推断表达式类型,编译阶段完成。这样就可以不用再写声明/定义的 变量的类型。
auto会忽略顶层const(最右),保留底层const,也就是保留对象本身的类型。如果需要保留顶层,需要显式声明const auto
// 由 val1 和 val2 相加的结果可以推断出 item 的类型
auto item = vall + val2; // item 初始化为 vall 和 val2 相加的结果
auto i = 0 , *p = &i ; // 正确: i是整数、 p 是整型指针
auto sz = 0 , pi = 3.14; // 错误 : sz 和 pi 的类型不一致
int i = 0, &r = i;
auto a = r; // a 是一个整数
// auto 对初始值的const的处理:抛弃顶层const,暴露底层const
const int ci = i, &er = ci;
auto b = ci ; // b是一个整数 (ci的顶层const特性被忽略掉了)
auto c = er; // c是一个整数(er是ci的别名,ci本身是一个顶层const)
auto d = &i; // d是一个整型指针(整数的地址就是指向整数的指针)
auto e = &ci; // e是一个指向整数常量的指针 (对常量对象取地址是一种底层const)
const auto f = ci; // ci的推演类型是int, f是const int
auto &g = ci; // g是一个整型常量引用,绑定到ci
auto &h = 42; // 错误:不能为非常量引用绑定字面值
const auto &j = 42 ; // 正确:可以为常量引用绑定字面值
auto k = ci , &l = i; // k是整数, l是整型引用
auto &m = ci, *p = &ci; // m是对整型常量的引用,p是指向整型常量的指针
auto &n = i , *p2 = &ci; // 错误:i的类型是int而&ci的类型是const int
decltype
decltype( expr ),编译器根据表达式推断类型,而不执行表达式。假如被调用,返回的类型。
decltype对const是保留全部const(包括顶层const),和auto不太一样。
decltype(f()) sum = x; //sum 的类型就是函数f的返回类型
const int ci = 0 , &cj = ci ;
decltype (ci) x = O; // x 的类型是 const int
decltype (cj) y = x; // y 的 类型是 const int&, y 绑定到变量 x
decltype (cj) z; //错误:z 是一个引用,必须初始化
//decltype 的结果可以是引用类型
int i = 42 , *p = &i , &r = i;
decltype(r + 0) b; //正确:加法的结果是int,因此b是一个(未初始化的)int
decltype(*p) c; //错误:e是int&,必须初始化
//decltype的表达式如果是加上了括号的变量, 结果将是引用
decltype((i)) d; //错误: d 是 int& ,必须初始化
decltype (i) e; //正确: e 是一个(未初始化的)int
五、自定义数据结构(struct、类)
struct Sales_data { /* . . . */ } accum, trans, *salesptr;
//与上一条语句等价,但可能更好一些
struct Sales data { /*. . . */ };
Sales_data accum, trans , *salesptr;