一、基本内置类型
如何选择类型:
- 明确数值不为负时,使用无符号类型
- 使用int执行整型运算,如果int无法表示,直接使用long long
- 只有在字符/布尔值时,采用char/bool类型
- 执行浮点数运算选择double,float精度太小,long double精度过高
类型转换:
- “bool = 算数类型”,算数类型非零则为true,否则为false
- “算数类型 = bool”,bool为false则为0,否则为1
- “整数 = 浮点数”,截断浮点数的小数部分
- “浮点数 = 整数”,小数部分记为0,但可能整数的值超过了浮点数的表达范围(会损失精度)
- “赋予无符号类型的一个超出其表达范围的值”,结果是该值对无符号类型可表达的最大值的取模后的余数
- “赋予无符号类型的一个超出其表达范围的值”,结果未定义,可能会出错
常量表示:
- 十进制、八进制、十六进制表示整数:20、024、0x14。后缀:u/U为unsigned,l/L为long,ll/LL为long long
- 浮点数:3.12356、3.12356E0(科学计数法)。后缀:f/F为float,l/L为long double,无后缀则默认为double
- 字符与字符串:‘A’(字符)、“A”(字符串)
- 转移字符:如“\n”、“\t”等
- 布尔值:false与ture
- 指针值:nullptr(为空时)
二、变量
变量定义:
- int value = 0, sum = value, cnt;//在定义变量时,可以进行初始化,也可以不初始化。当然也可使用已经初始化的变量来初始化当前定义的变量
变量初始化(创建变量时赋予初始值):
- double price = 19.8, discount = price * 0.16;
- double salePrice = applyDiscount(price, discount);//可使用函数返回值为变量进行初始化
变量赋值(用新值来替代对象的当前值):
- price = 20.8;
初始化方式:
- int i = 0;
- int i = {0};//列表初始化,当初始值存在丢失的风险时,编译器会报错
- int i{0};//列表初始化,当初始值存在丢失的风险时,编译器会报错
- int i(0);
默认初始化:
- 变量在函数之外,默认初始化为0
- 变量在函数之内,其值未定义,由改位置原本的值决定
- 类的对象,由该类决定
变量的声明与定义:
- 声明:使得名字为程序所知,一个文件想要使用别的文件定义的变量,则必须包含该变量的声明。例如:extern int i;(声明i,不会申请空间)
- 定义:负责创建于变量名相关联的实体。例如:int i;(声明并定义i,会申请空间)
- 变量只能被定义一次,但是可以被多次声明。“extern int i = 1;”此语句是定义语句,在函数体内部,不能初始化一个由extern关键字标记的变量
标识符:
- 有字符、数字和下划线组成,其中数字不能作为开头
- 用户定义的标识符不能连续出现两个下划线
- 不能以下划线紧连大写字符开头
- 不能和关键字相同
变量名作用域:
- 同一个名字在不同的作用域中可能指向不同实体
- 名字的有效区域起始于名字的声明语句,以声明语句所在的作用域末端结束
- 全局作用域:例如main函数和全局变量均具有全局作用域,在整个程序范围内均可使用
- 块作用域:如果在main函数内部定义一个sum,那么sum的作用域就是从定义开始,到main函数结束
- 嵌套作用域:被韩寒的作用域称为内层作用域,包含着别的作用域的作用域称为外层作用域,当处于内层作用域时,除非使用作用域操作符,否则外层同名变量会被内层同名变量所屏蔽,例如如下所述:
#include<iostream> int reused = 42;//变量reused具有全局作用域 int main(void) { int unique = 0; std::cout << reused << " " << unique << std::endl; int reused = 0;//新建局部变量reused,覆盖了全局变量reused //输出局部变量reused的值,为0 std::cout << reused << " " << unique << std::endl; //输出全局变量reused的值,为42,因为使用了"::"显示访问全局变量 std::cout << ::reused << " " << unique << std::endl; return 0; }
三、复合类型
复合类型:引用与指针
引用:
- 引用和对象绑定在一起,而不是将对象的值拷贝给引用。引用必须用对象进行初始化
- 引用的类型必须与其所引用的对象的类型一致(但有两个例外)
- 引用本身不是一个对象,不可以定义引用的引用
- 通过操作引用,就是在操作与引用绑定在一起的对象
int val = 2; //定义一个int型变量 int &refval_1 = val; //定义一个引用refval_1,将其与val绑定在一起 refval_1 = 4; //通过修改引用refval_1,从而修改val int &refval_2 = refval; //注意,此处并不是定义引用的引用,此语句是正确的,refval_2也与val绑定 int &refval_3 = 10; //错误,引用类型的初始值必须是一个对象 double &refval_3 = val; //错误,引用类型的初始化必须是double型
指针:
- 指针指向另一种类型的对象,存储该对象的地址,指针不是一定要初始化,但是建议定义时进行初始化
- 指针的类型必须与所指向的对象的类型一致(但有两个例外)
- 指针本身也是一个对象,允许指针进行拷贝和赋值,并且可以先后指向同种类型的不同对象
- 通过指针操作其指向的对象时,需要用到解引用符”*“,表示取指针的地址处的内容,即得到指向对象的地址,通过操作该地址,从而操作该对象
- 当指针拥有合法值时,可以用于条件表达式
- 对于类型相同的两个合法指针,可以使用”==“和’!=‘来比较两个指针
int val = 2; //定义并初始化变量val int *pv_1 = &val; //定义整型指针pv_1指向val int *pv_2 = pv_1; //定义pv_2,通过获取pv_1的值,指向val *pv_1 = 3; //通过指针,修改val的值,此时val == 3 if (pv_1) //可用于条件表达式 std::cout << *pv_1 << std::endl; else std::cout << "the piont is nullptr!" << std::endl; if (pv_1 == pv_2) //可用”==“进行比较两个指向相同数据类型的指针 std::cout << "pv_1 and pv_2 are the same!" << std::endl; else std::cout << "pv_1 and pv_2 are n0t the same!" << std::endl;
指针的指针:
int val = 2; int *pv = &val; int **ppv = &pv; //指向pv,ppv存储pv的地址
指针的引用:
int val = 2; //定义并初始化整型变量val int *p; //定义一个指针p int *&r = p; //定义指针p的引用r r = &val; //r指向val,此时,p也指向val,因为r与p是绑定在一起的 *r = 0; //通过操作r,来修改val的值,当然,通过p也可以修改
tips:面对一条比较复杂的指针或引用的声明语句,从右向左阅读有助于弄清楚它的涵义
“&”和“*”。当它们在声明中,分别表示“引用”、“指针”。当它们在运算中,分别表示“取地址”、“取该地址处的内容”
四、const限定符
const限定符
- const类型的对象,不可以执行非const对象中改变其内容的操作。例如,赋值操作就不允许。并且const对象必须初始化
const int i = get_size(); //正确,运行时初始化 const int j = 42; //正确,编译时初始化 const int k; //不正确,没有进行初始化
- 如果需要在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字,并且在其它文件中用extern进行声明
//file.cc extern const int bufSize = fcn(); //file.h extern const int bufSize;
- 可以定义const的引用,但是不可以通过该引用来修改const所修饰的对象的值,也不可以试图让一个非常量引用指向常量对象
- 对常量的引用:初始化常量引用时允许任意表达式作为初始值,只要表达式可转化成引用的类型即可(此种情况引用的类型与引用对象的类型可以不一致)。
- 对const的引用,可以引用一个非const的对象(此时,当引用的对象的值被修改时,const引用的值也随之变化)
int val; const int &ref_1 = val; //常量引用可与一个非常量对象绑定 const int sum; const int &ref_2 = sum; //常量引用可与一个常量对象绑定 int &ref_3 = sum; //错误,非常量引用不可与常量对象绑定 /*接下来才是惊掉下巴的*/ double val_2 = 3.14159; //此处加不加const,下面的语句均正确 const int &ref_4 = val_2; //正确,编译器由double类型生成一个临时的整型常量,将ref_4与该临时常量绑定在一起
- 指向常量的指针:不能修改其指向对象的值,但是,并不要求所指向的对象必须是常量对象(此种情况指针的类型与所指向对象的类型可以不一致)
const int val = 3; //声明一个常量对象val int *ptr_1 = &val; //错误,ptr_1是普通指针,无法指向常量对象 consth int *ptr_2 = &val; //正确,ptr_2是指向常量的指针,可以指向常量对象 *ptr_2 = 42; //错误,不能通过ptr_2修改一个常量对象 int sum = 5; ptr_2 = ∑ //正确,ptr_2是指向常量的指针,但是也可以指向非常量对象 *ptr_2 = 6; //正确,因为sum是非常量对象,可以通过ptr_2进行修改
- 常量指针:必须初始化。初始化后,其值(所指向的地址)将不再改变,但是可以通过该常量指针修改指向的对象
int val, sum; int *const ptr = &val; *ptr = 5; //正确,ptr声明为常量指针,可以通过ptr来修改val的值 ptr = ∑ //错误,ptr声明为常量指针,不可以修改ptr的指向
- 指向常量的常量指针:其指向的对象可以是非常量对象(此时对象可以改变)。如果是指向常量对象,则此时指针的值不能改变,指向的对象也不能改变。
int val, sum; const int *const ptr = &val; val = 5; //正确,因为val并不是常量 *ptr = 5; //错误,ptr声明为指向常量的常量指针,不可以通过ptr修改val ptr = ∑ //错误,ptr声明为指向常量的常量指针,不可以修改ptr的指向 /*总结: *如果一个指针声明为指向常量的指针,那么可以指向常量对象,也可以指向非常量对象 *指针的指向不能发生改变 *如果指向的是常量对象,不可以通过任何形式修改该常量对象 *如果指向的是非常量对象,可以通过除用该指针间接修改该对象外,可以使用其它方式修改该对象*/
- 顶层const与底层const:顶层const表示变量本身是一个常量,底层const表示指针指向的对象是一个常量
int val = 0; int *const p1 = &val; //不能改变p1的值,为顶层const const int ci = 42; //不能改变ci的值,为顶层const const int *p2 = &ci; //可以改变p2的值,但是不能该p2指向的对象的值,为底层const
- 常量表达式:值不会变化并且在编译过程中即可得到计算结果的表达式
- constexpr:使用constexpr必须满足变量是一个常量,并且该变量必须使用常量表达式或constexpr函数来初始化该变量(变量的类型应该是字面值类型,不可以是自定义的、IO库的类型)
- 在constexpr声明中,如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指向的对象无关
const int max_files = 20; //是常量表达式 const int limit = max_files + 1; //是常量表达式 int staff_size = 27; //数据类型是普通int型,不是常量表达式 const int sz = get_size(); //sz的具体值需要运行时方可获得,不是常量表达式 constexpr int sz = size(); //如果size()是constexpr函数,该声明是正确的,否则是错误的 const int *ptr = nullptr; //ptr是一个指向常量的指针 constexpr int *q = nullptr; //q是一个常量指针,其指向的对象的值可以被修改 constexpr int i = 42; int j = 0; //i和j都必须定义在函数体之外,因为函数内的局部变量在编译时仍然未知,在运行时才会被压入栈中 constexpr const int * p = &i; //p是一个常量指针,指向整型常量i constexpr int *p1 = &j; //p1是一个常量指针,指向整数j
五、处理类型
类型别名
使用类型别名,可以提高可读性,有助于程序员清楚地知道使用该类型地真实目的
- 关键字typedef
typedef double wages; //wages是double的同义词 typedef wages base, *p; //base是double的同义词,p是double*的同义词 /*类型的别名与类型名是等价的,只要类型名可以出现的地方,就可以使用类型别名*/ wages hourly, weekly; //等价于double hourly, weekly; /*指针、常量与类型别名*/ typedef char *pstring; //pstring是char *的同义词,即为字符指针 const pstring cstr = nullptr; //cstr是指向字符的常量指针 const pstring *ps; //ps是一个指针,它的对象是指向char的常量指针
- 别名声明——using
using SI = Sales_item; /*SIs是Sales_item的同义词, *此方法用关键字using作为别名声明的开始,其后紧跟别名和等号 *作用是把等号左侧的名字规定成右侧的类型的别名*/
auto类型说明符
- 使用auto说明符,能让编译器替我们分析表达式所属的类型,然后推算变量的类型,因此,auto定义的变量必须有初始值
- 使用auto也能一条语句声明多个变量。一条声明语句只能由一个基本类型,所以该语句中所有变量的初始基本数据类型均应相同
auto i = 0, *p = &i; //正确,i为int,p为指向int的指针 auto sz = 0, pi = 3.14; //错误,sz和pi的类型不一致
- 有时候,auto推断出来的类型并不与初始值的类型完全相同
/*使用引用进行初始化,得到的类型与引用对象的类型一致*/ int i = 0, &r = i; auto a = r; //a的类型为int /*auto会忽略顶层const,而保留底层const*/ const int ci = i, &cr = ci; auto b = ci; //b的类型为int(ci的顶层const被忽略) auto c = cr; //c的类型为int(cr是ci的别名,ci是顶层const) auto d = &i; //d的类型为int* auto e = &ci; //e的类型为指向整数常量的指针 /*希望推断处auto类型的一个顶层const*/ const auto f = ci; //f是const int /*设置一个类型为auto的引用时,初始值中的顶层const被保留*/ auto &g = ci; //g是一个整型常量引用,绑定到ci const auto &j = 42; //j是一个常量引用,绑定到字面值 /*&和*只从属于某个声明符,而非基本数据类型的一部分,因此初始值必须是同一类型*/ auto k = ci, &l = i; //k是整数,l是整数引用 auto &m = ci, *p = &ci; //m是整数常量的引用,p是指向整数常量的指针
decltype类型指示符
使用decltype可以从表达式中推断出要定义的变量的类型,并且不使用该表达式的值初始化变量。decltype的作用是选择并返回操作数的数据类型,在此过程中,编译器分析表达式得到其类型,但没有真正计算表达式的值
decltype ((f()) sum = x; //sum的类型是f()的返回值,初始化为x /*decltype保留顶层const*/ const int ci = 0, &cr = ci; decltype(ci) x = 0; //x的类型为const int,初始化为0 decltype(cj) y = x; //y的类型为const int&,与x绑定 decltype(cj) z; //错误,z为const int&,必须初始化 /*如果decltype使用的是表达式时,结果为表达式的返回类型*/ int i = 42, *p = &i, &r = i; decltype(r + 0) b; //加法的结果是int,b为int类型 decltype(*p) c; //错误,*p为引用,c为int&,必须初始化 /*如果表达式的内容是解引用,则返回类型是引用*/ /*如果变量加上一层或多层括号,编译器会将其视为表达式,返回其引用类型*/ decltype(i), d; //d为int类型 decltype((i), e = d; //e为int&类型
六、自定义数据结构
自定义数据结构
数据结构是一组相关的数据元素组织起来,同时提供操作数据的方法
- struct:
struct Sales_data { std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }data_1, data_2; //data_1、data_2是定义的对象 /*每个对象由自己的一份数据成员的拷贝 *可以为数据成提供一个类内初始值,在创建类时,类内初始值将用于初始化数据成员 *没有初始值的成员使用默认初始化*/
- 编写头文件
/*头文件通常包括只能被定义一次的实体,例如类、const、constexpr变量 *头文件一旦改变,相关的源文件必须重新编译以获得更新过的声明*/ #ifndef SALES_DATA_H #define SALES_DATA_H #include<iostream> struct Sales_data { std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; #endif /*头文件保护符依赖于预处理变量,预处理变量有两种状态:已定义和未定义 *#define指令把一个名字设为预处理变量 *#ifndef用于检查预处理变量是否被定义,如果未定义,则进行定义 *如果已定义,则跳过定义字段 *预处理变量无视c++中关于作用域的规则,在整个程序中,预处理变量包括头文件描述符必须唯一*/