一、C++基础
1. 变量和基本类型
1.1 基本数据类型
注意:
(1)如果你的数值超过的了int的表示范围,选用long long
(2)char 在一些机器上是有符号的,而在另一些机器上又是无符号的
(3)执行浮点数运算选用double,这是因为float通常精度不够而且双精度浮点数和单精度浮点数的计算代价相差无几
类型转换
(1)浮点->整型:截断
(2)赋予无符号类型一个超出其表示范围的值:结果为该值对无符号类型表示数值总数取模后的余数
(3)赋予带符号类型一个超出其表示范围的值:结果为未定义的
(4)当算术表达式中既有无符号数又有int值时,会将int值转换为无符号数
注意:// 错误:变量u永远也不会小于0,循环条件会一直成立 for (unsigned u = 10; u >= 0; --u) { std::out << u << std::endl; }
字面量常值
定义:指的是直接输入到程序中的值。比如:in myAge=26; myAge是一个int类型变量,而26是一个字面常量。
注意: 编译器在每个字符串的结尾处添加一个空字符(‘\0’),因此,字符串字面值的实际长度要比它的内容多1。
c++的转义序列汇总:
1.2 变量
定义: 变量提供一个具名的、可供程序操作的存储空间
基本形式:首先是类型说明符,随后紧跟由一个或多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束
初始化
注意: 初始化不是赋值!
初始化:创建变量时赋予其一个初始值
赋值:把对象的当前值擦除,而以一个新值来代替
四种方式:int a = 0; int a = {0}; int a{0}; int a(0);
列表初始化
定义:使用一组由花括号括起来的初始值进行初始化变量
默认初始化
定义变量是没有指定初始值
默认值是由变量类型决定
注意:
(1)定义与任何函数体之外的变量被初始化为0
(2)定义与函数体内部的内置类型变量将不被初始化,其值为未定义。类的对象如果没有显式地初始化,则其值由类确定
(3)未初始化的变量含有一个不确定的值,易出错
变量声明和定义的关系
分离式编译:允许将程序分割为若干个文件,每个文件可被独立编译
将声明和定义区分开
声明:规定变量的类型和名字
定义:负责创建与名字关联的实体,还会申请空间,也可能会为变量赋予一个初始值。
关键字:extern ;不能用于函数体内部extern int i; // 声明i而非定义i int j; // 声明并定义j extern double pi = 3.1416; // 定义pi
注: 变量只能被定义一次,但可以被多次声明
标识符
由字母、数字和下划线组成。必须以字母或下划线开头,对大小写敏感
用户自定义的标识符中:
(1)不能连续出现两个下划线
(2)也不能以下划线紧接大写字母开头
(3)定义在函数体外的标识符不能以下划线开头
命名规范
标识符要能体现实际含义
变量名一般用小写
用户自定义的类名一般以大写字母开头
如果标识符由多个单词组成,则单词间应有明显区分,如:student_loan或者studentLoan
名字的作用域
1. 全局作用域
2. 块作用域
3. 嵌套的作用域: 内层作用域、外层作用域
1.3 复合类型
定义:基于其它类型定义的类型。如引用、指针
引用
定义:为对象起了另一个名字。C++11新增”右值引用“,一般”引用“指”左值引用“。
注意:
(1)引用必须初始化;
(2)定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用;
(3)引用不能重新绑定到另一个对象,因此必须初始化;
(4)引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起;int val = 10; int &refVal = ival; // refVal指向val(是val的另一个名字) int &ref = 10; // 错误!引用类型的初始值必须是一个对象 double a = 3.14; int &ref = a; // 错误!此处的引用类型的初始值必须时一个int型对象
指针
定义:指针是“指向”另外一种类型的复合类型;其本身就是一个对象,允许对指针赋值和拷贝,无须在定义时赋初值;
注意:
(1)如果在一条语句中定义了几个指针变量,每个变量前面都必须有符号*int *ip1, *ip2; // ip1和ip2都是指向int型对象的地址 double dp, *dp2;// dp2是指向double型对象的指针,do是double型对象
(2)指针存放某个对象的地址,要想获取该地址,需要使用取地址符&
double dval; double *pd = &dval; // 正确,初始值是double型对象的地址 double *pd2 = pd; // 正确:初始值是指向double对象的指针 int *pi = pd; // 错误:指针pi的类型和pd的类型不匹配 pi = &dval; // 错误:试图吧double型对象的地址赋给int型指针
(3)一般要求初始化所有的指针
指针值
四种状态:
- 指向一个对象
- 指向紧邻对象所占空间的下一个位置
- 空指针,意味着指针没有指向任何对象
- 无效指针,也就是上述情况之外的其他值
利用指针访问对象
利用**解引用符(*)**访问指针所指对象
若对解引用的结果赋值,实际上也就是给指针所指对象赋值
注: 解引用的操作仅适用于那些确实指向了某个对象的有效指针
空指针
生成空指针的方法:int *p1 = nullptr; int *p2 = 0; int *p3 = NULL;
注: NULL为预处理变量,其在头文件cstdlib中定义,其值为0;
赋值和指针
赋值永远改变的是等号左侧的对象
给指针赋值就是令它存放一个新的地址,从而指向一个新的对象
其它指针操作
任何非0指针都为true
两个指针比较是比较其地址
注意: 一个指针指向某对象,同时另一个指针指向另外对象的下一地址,此时也有可能出现者两个指针值相同的情况,即指针相等
void* 指针
void* 是一种特殊的指针类型,可用于存放任意对象的地址。但不了解存放的地址中是什么类型的对象
不能直接操作void*指针所指的对象double obj = 3.14, *pd = &obj; void *pv = &obj; // void*能存放任意类型对象的地址 pv = pd;
理解复合类型的声明
虽然基本数据类型只有一个,但声明符的形式却可以不同。如:// i是一个int型的数,p是一个int型指针,r是一个int型引用 int i = 1024, *p = &i, &r = i;
- 定义多个变量
易错点:int *p1, p2; // p1是指向int的指针,p2是int
(1)把修饰符和变量标识符写在一起
(2)把修饰符和类型名写在一起,并且每条语句只定义一个变量- 指向指针的指针(**表示)
指针是内存中的对象,向其他对象一样也有自己的地址,因此允许把指针再存放到另一个指针当中- 指向指针的引用
注:int i = 42; int *p; // p 是一个int型指针 int *&r = p; // r 是一个对指针p的引用 r = &i; // r 引用了一个指针,因此给r赋值&i就是令p指向i *r = 0; // 解引用r得到i,也就是p指向的对象,将i的值改为0’
(1)从右往左阅读r的定义
(2)离变量名最近的符号对变量的类型有最直接的影响
(3)声明符的其余部分用以确定r引用的类型是什么
1.4 const 限定符
对变量的类型进行限定,使其值不能被改变
const对象必须初始化
初始化和const
const类型的对象只能执行不改变其内容的操作
注意:
(1)可以用非const初始化const,也可以用const初始化非const
(2)默认状态下,const对象仅在文件内有效
(3)默认情况下,const对象被设定为仅在文件内有效
(4)当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量
(5)若只在一个文件中定义const,而在其它多个文件中声明并使用它,则不管在定义还是声明都需要天剑extern关键字// file_1.cpp定义并初始化了一个常量,该常量能被其它文件访问 extern const int bufSize = fcn(); // file_1.h头文件 extern const int bufSize; // 与file_1.cpp中定义的bufSize是同一个
const的引用
把引用绑到const对象上,称为对常量的引用(常量引用)。
对常量的引用不能被用作修改它所绑定的对象const int ci = 1024; const int &r1; //正确:引用及其对应的对象都是常量 r1 = 42; // 错误:r1是对常量的引用 int &r2 = ci; // 错误:试图让一个非常量引用指向一个常量对象
注意: c++语言并不允许随意改变引用所绑定的对象,所以可以理解为引用都算常量
初始化和对const的引用
(1)在初始化常量引用时允许用任意表达式作为初始值,只需该表达式可以转换为引用类型即可
(2)允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式int i = 42; const int &r1 = i; // 允许将const int&绑定到一个普通int对象上 const int &r2 = 42; // 正确:r2是一个常量引用 const int &r3 = r1 * 2; // 正确:r3是一个常量引用 int &r4 = r1 * 2; // 正确:r4是一个普通的非常量引用
理解内部原理
// 注意此处引用的是临时量 double dval = 3.14; const int &ri = dval;
// 内部过程 const int temp = dval; // 由双精度浮点数生成一个临时的整型变量 const int &ri = temp; // 让ri绑定这个临时量;
注意:
(1)如果ri不是常量引用的时候,按照上述引用会为非法
(2)对const的引用可能引用一个并非const的对象int i = 42; int &r1 = i; // 引用ri绑定对象i const int &r2 = i; // r2也绑定对象i,但是不允许通过r2修改i的值 r1 = 0; // r1并非常量,i的值修改为0 r2 = 0; // 错误:r2是一个常量引用
指针和const
指向常量的指针不能用于改变其所指对象的值;要想存放常量对象的地址,只能使用指向常量的指针;
注: 指向常量的指针不一定其所指的对象必须是一个常量const double pi = 3.14; // pi是个常量,它的值不能改变 double *ptr = π // 错误:ptr是一个普通指针 const doube *cptr = π // 正确:cptr可以指向一个双精度常量 *cptr = 42; // 错误:不能给*cptr赋值 double dval = 3.14; cptr = &dval; // 正确:但是不能通过cptr改变dval的值
const指针
指针是对象而引用不是
常量指针必须初始化,其值(存放在指针的地址)就不能再改变
顶层const
顶层const:表示指针本身是个常量;常量指针
底层const:表示指针所指的对象是一个常量;指针常量int i = 0; int *const p1 = &i; // 不能改变p1的值,这是一个顶层const; const int ci = 42; // 不能改变ci的值,这是一个顶层const; const int *p2 = &ci; // 允许改变p2的值,这是一个底层const; const int *const p3 = p2; // 靠右的const是顶层const,靠左的是底层const; const int &r = ci; // 用于声明引用的const都是底层const;
注意:
(1)执行拷贝操作时:
- 顶层const不受影响
- 底层const,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换
constexpr和常量表达式
常量表达式:是指值不会改变并且在编译过程就能得到计算结果的表达式
constexpr变量
声明为constexpr类型的变量一定是一个常量constexpr int mf = 20; // 20 是常量表达式 constexpr int limit = mf + 1; // mf + 1是常量表达式 constexpr int sz = size(); // 只有当size()是一个constexpr函数时,才是一条正确的声明语句
注: 若认定变量是一个常量表达式,则应把它声明成constexpr类型
字面值类型
声明为常量表达式的值必须是字面值类型;
包括算术类型、引用和指针;
一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象
注: 函数体内定义的变量一般来说并非存放在固定地址中,
指针和constexpr
注: 在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关;const int *p = nullptr; // p是一个指向整型常量的指针 constexpr int *q = nullptr; // q是一个指向整数的常量指针
1.5 处理类型
类型别名
定义:类型别名是一个名字,它是某种类型的同义词
方式:
- 利用关键字typedef
typedef double wages; // wages是double的同义词 typedef wages base, *p; // base是double的同义词,p是double*的同义词
- 使用别名声明;利用using关键字,将等号左侧的名字规定成等号右侧类型的别名
using SI = Sales_items; // SI 是Sales_item的同义词
指针、常量和类型别名
typedef char *pstring; const pstring cstr = 0; // cstr 是指向char的 常量指针 const pstring *ps; // ps是一个指针,它的对象是指向char的 常量指针
注意: 不能简单地将pstring替换为char*!!!!
声明中用到pstring时,其基本数据类型是指针,但用了char*重写后,数据类型就变成了char,*成为声明符的一部分。
auto 类型说明符
auto让编译器通过初始值来推算变量的类型
auto也能在一条语句中声明多个变量,但需要求该语句中所有变量的初始基本数据类型都必须一样auto i = 0, *p = &i;
复合类型、常量和auto
(1)编译器推断出来的auto类型有时候和初始值的类型并不完全一样
(2)auto一般会忽略掉顶层const,同时底层const则会保留下来const int ci = i, &cr = ci; auto b = ci; // b是一个整数(ci的顶层const特性被忽略掉了) auto c = cr; // c是一个整数(cr是ci的别名,ci本身是一个顶层const) auto d = &i; // d是一个整型指针(整数的地址就是指向整数指针) auto e = &ci; // e是一个指向整数常量的指针(对常量对象取地址是一种底层const)
(3)若希望推断出的auto类型是一个顶层const,则需明确指出
const auto f = ci; // ci的推断类型是int,f是const int;
(4)将引用类型设置为auto
auto &g = ci; const auto &j = 42;
decltype类型指示符
作用:选择并返回操作数的数据类型;仅得到类型,并不计算表达式的值。decltype(f()) sum = x; // sum 的类型就是函数f的返回类型
若decltype使用的表达式是一个变量,则decltype返回该变量的类型(包含顶层const和引用在内)
decltype和引用
如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型>// decltype的结果可以是引用类型 int i = 42, *p = &i, &r = i; decltype(r + 0) b; // 正确:加法的结果是int,因此b是一个(未初始化的)int decltype(*p) c; // 错误:c是int&,必须初始化
注意:
(1)若decltype使用的是一个不加括号的变量,则得到的结果就是该变量的类型
(2)如果给变量加上了一层或多层括号,编译器就会把它当成一个表达式
(3)变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型// decltype的表达式如果是加上了括号的变量,结果是引用 decltype((i)) d; // 错误:d是int&,必须初始化 decltype(i) e; // 正确:e是一个(未初始化的)int
注意: decltype((var))(双层括号)的结果永远是引用,而decltype(var)的结果只有当var本身就是一个引用的时候才是引用
自定义数据结构
以关键字struct开始,紧跟着类名和类体struct Sales_data { std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; };
注意: 要在类定义的最后加上分号,没有初始化的会被默认初始化
编写自己的头文件
为了确保各个文件中类的定义一致,类通常被定义在头文件中,而且类所在头文件的名字应与类的名字一样
预处理器概述
定义:确保头文件多次包含仍能安全工作的常用技术
预处理变量有两种状态:已定义和未定义
(1)#define 指令把一个名字设定为预处理变量
(2)检查某个指定的预处理变量是否已经定义:#ifdef 当且仅当变量已定义时为真,#ifndef当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直至遇到#endif指令为止