目录
变量的声明(declaration) vs 定义(define)
第一章 开始
熟悉编译器
g++:
-
编译:
g++ --std=c++11 ch01.cpp -o main
-
运行:
./prog1
-
查看运行状态:
echo $?
-
编译多个文件:
g++ ch2.cpp Sales_item.cc -o main
输入 g++ --help
,查看编译器选项:
Usage: g++ [options] file... Options: -pass-exit-codes Exit with highest error code from a phase --help Display this information --target-help Display target specific command line options --help={common|optimizers|params|target|warnings|[^]{joined|separate|undocumented}}[,...] Display specific types of command line options (Use '-v --help' to display command line options of sub-processes) --version Display compiler version information -dumpspecs Display all of the built in spec strings -dumpversion Display the version of the compiler -dumpmachine Display the compiler's target processor -print-search-dirs Display the directories in the compiler's search path -print-libgcc-file-name Display the name of the compiler's companion library -print-file-name=<lib> Display the full path to library <lib> -print-prog-name=<prog> Display the full path to compiler component <prog> -print-multiarch Display the target's normalized GNU triplet, used as a component in the library path -print-multi-directory Display the root directory for versions of libgcc -print-multi-lib Display the mapping between command line options and multiple library search directories -print-multi-os-directory Display the relative path to OS libraries -print-sysroot Display the target libraries directory -print-sysroot-headers-suffix Display the sysroot suffix used to find headers -Wa,<options> Pass comma-separated <options> on to the assembler -Wp,<options> Pass comma-separated <options> on to the preprocessor -Wl,<options> Pass comma-separated <options> on to the linker -Xassembler <arg> Pass <arg> on to the assembler -Xpreprocessor <arg> Pass <arg> on to the preprocessor -Xlinker <arg> Pass <arg> on to the linker -save-temps Do not delete intermediate files -save-temps=<arg> Do not delete intermediate files -no-canonical-prefixes Do not canonicalize paths when building relative prefixes to other gcc components -pipe Use pipes rather than intermediate files -time Time the execution of each subprocess -specs=<file> Override built-in specs with the contents of <file> -std=<standard> Assume that the input sources are for <standard> --sysroot=<directory> Use <directory> as the root directory for headers and libraries -B <directory> Add <directory> to the compiler's search paths -v Display the programs invoked by the compiler -### Like -v but options quoted and commands not executed -E Preprocess only; do not compile, assemble or link -S Compile only; do not assemble or link -c Compile and assemble, but do not link -o <file> Place the output into <file> -pie Create a position independent executable -shared Create a shared library -x <language> Specify the language of the following input files Permissible languages include: c c++ assembler none 'none' means revert to the default behavior of guessing the language based on the file's extension
输入 g++ -v --help
可以看到更完整的指令。 例如还有些常用的:
-h FILENAME, -soname FILENAME: Set internal name of shared library -I PROGRAM, --dynamic-linker PROGRAM: Set PROGRAM as the dynamic linker to use -l LIBNAME, --library LIBNAME: Search for library LIBNAME -L DIRECTORY, --library-path DIRECTORY: Add DIRECTORY to library search path
获得程序状态:
-
windows:
echo %ERRORLEVEL%
-
UNIX:
echo $?
IO
-
#include <iostream>
-
std::cout << "hello"
-
std::cin >> v1
记住>>
和<<
返回的结果都是左操作数,也就是输入流和输出流本身。
endl:这是一个被称为操纵符(manipulator)的特殊值,效果是结束当前行,并将设备关联的缓冲区(buffer)中的内容刷到设备中。
UNIX和Mac下键盘输入文件结束符:ctrl+d
,Windows下:ctrl+z
头文件:类的类型一般存储在头文件中,标准库的头文件使用<>
,非标准库的头文件使用""
。申明写在.h
文件,定义实现写在.cpp
文件。
避免多次包含同一头文件:
#ifndef SALESITEM_H #define SALESITEM_H // Definition of Sales_itemclass and related functions goes here #endif
成员函数(类方法):使用.
调用。
命名空间(namespace):使用作用域运算符::
调用。
注释
-
单行注释:
//
-
多行注释:
/**/
。编译器将/*
和*/
之间的内容都作为注释内容忽略。注意不能嵌套。#define SALESITEM_H /* * 多行注释格式 * 每一行加一个* */
while语句
循环执行,(直到条件(condition)为假。
for语句
循环头由三部分组成:
-
一个初始化语句(init-statement)
-
一个循环条件(condition)
-
一个表达式(expression)
使用文件重定向
./main <infile >outfile
第二章 变量和基本类型
任何常用的编程语言都具备一组公共的语法特征,最基本的特征包括:
-
整型、字符型等内置类型
-
变量,用来为对象命名
-
表达式和语句,用于操作上述数据类型的具体值
-
if 或 while 等控制结构,有选择地执行一些语句或重复地执行一些语句
-
函数,用于定义可供随时调用的计算单元
大多数编程语言通过两种方式来进一步补充其基本特征:
-
自定义数据类型,实现对语言的扩展
-
将一些有用的功能封装成库函数
基本内置类型
基本算数类型:
类型 | 含义 | 最小尺寸 |
---|---|---|
bool | 布尔类型 | 8bits |
char | 字符 | 8bits |
wchar_t | 宽字符 | 16bits |
char16_t | Unicode字符 | 16bits |
char32_t | Unicode字符 | 32bits |
short | 短整型 | 16bits |
int | 整型 | 16bits (在32位机器中是32bits) |
long | 长整型 | 32bits |
long long | 长整型 | 64bits (是在C++11中新定义的) |
float | 单精度浮点数 | 6位有效数字 |
double | 双精度浮点数 | 10位有效数字 |
long double | 扩展精度浮点数 | 10位有效数字 |
如何选择类型
-
1.当明确知晓数值不可能是负数时,选用无符号类型;
-
2.使用
int
执行整数运算。一般long
的大小和int
一样,而short
常常显得太小。除非超过了int
的范围,选择long long
。 -
3.算术表达式中不要使用
char
或bool
。 -
4.浮点运算选用
double
。
类型转换
-
非布尔型赋给布尔型,初始值为0则结果为false,否则为true。
-
布尔型赋给非布尔型,初始值为false结果为0,初始值为true结果为1。
字面值常量
-
一个形如
42
的值被称作字面值常量(literal)。-
整型和浮点型字面值。
-
字符和字符串字面值。
-
使用空格连接,继承自C。
-
字符字面值:单引号,
'a'
-
字符串字面值:双引号,
"Hello World""
-
分多行书写字符串。
std:cout<<"wow, a really, really long string" "literal that spans two lines" <<std::endl;
-
-
转义序列。
\n
、\t
等。 -
布尔字面值。
true
,false
。 -
指针字面值。
nullptr
-
字符串型实际上时常量字符构成的数组,结尾处以
'\0'
结束,所以字符串类型实际上长度比内容多1。
变量
变量提供一个具名的、可供程序操作的存储空间。 C++
中变量和对象一般可以互换使用。
变量定义(define)
-
定义形式:类型说明符(type specifier) + 一个或多个变量名组成的列表。如
int sum = 0, value, units_sold = 0;
-
初始化(initialize):对象在创建时获得了一个特定的值。
-
初始化不是赋值!:
-
初始化 = 创建变量 + 赋予初始值
-
赋值 = 擦除对象的当前值 + 用新值代替
-
列表初始化:使用花括号
{}
,如int units_sold{0};
-
默认初始化:定义时没有指定初始值会被默认初始化;在函数体内部的内置类型变量将不会被初始化。
-
建议初始化每一个内置类型的变量。
-
变量的声明(declaration) vs 定义(define)
-
为了支持分离式编译,
C++
将声明和定义区分开。声明使得名字为程序所知。定义负责创建与名字关联的实体。 -
extern:只是说明变量定义在其他地方。
-
只声明而不定义: 在变量名前添加关键字
extern
,如extern int i;
。但如果包含了初始值,就变成了定义:extern double pi = 3.14;
-
变量只能被定义一次,但是可以多次声明。定义只出现在一个文件中,其他文件使用该变量时需要对其声明。
-
名字的作用域(namescope)
{}
-
第一次使用变量时再定义它。
-
嵌套的作用域
-
同时存在全局和局部变量时,已定义局部变量的作用域中可用
::reused
显式访问全局变量reused。 -
但是用到全局变量时,尽量不适用重名的局部变量。
-
-
变量命名规范
-
需体现实际意义
-
变量名用小写字母
-
自定义类名用大写字母开头:Sales_item
-
标识符由多个单词组成,中间须有明确区分:student_loan或studentLoan,不要用studentloan。
左值和右值
-
左值(l-value)可以出现在赋值语句的左边或者右边,比如变量;
-
右值(r-value)只能出现在赋值语句的右边,比如常量。
复合类型
引用
一般说的引用是指的左值引用
-
引用:引用是一个对象的别名,引用类型引用(refer to)另外一种类型。如
int &refVal = val;
。 -
引用必须初始化。
-
引用和它的初始值是绑定bind在一起的,而不是拷贝。一旦定义就不能更改绑定为其他的对象
指针
int *p; //指向int型对象的指针
-
是一种
"指向(point to)"
另外一种类型的复合类型。 -
定义指针类型:
int *ip1;
,从右向左读有助于阅读,ip1
是指向int
类型的指针。 -
指针存放某个对象的地址。
-
获取对象的地址:
int i=42; int *p = &i;
。&
是取地址符。 -
指针的类型与所指向的对象类型必须一致(均为同一类型int、double等)
-
指针的值的四种状态:
-
1.指向一个对象;
-
2.指向紧邻对象的下一个位置;
-
3.空指针;
-
4.无效指针。
-
对无效指针的操作均会引发错误,第二种和第三种虽为有效的,但理论上是不被允许的
-
-
指针访问对象:
cout << *p;
输出p指针所指对象的数据,*
是解引用符。 -
空指针不指向任何对象。使用
int *p=nullptr;
来使用空指针。 -
指针和引用的区别:引用本身并非一个对象,引用定义后就不能绑定到其他的对象了;指针并没有此限制,相当于变量一样使用。
-
赋值语句永远改变的是左侧的对象。
-
void*
指针可以存放任意对象的地址。因无类型,仅操作内存空间,对所存对象无法访问。 -
其他指针类型必须要与所指对象严格匹配。
-
两个指针相减的类型是
ptrdiff_t
。 -
建议:初始化所有指针。
-
int* p1, p2;//*是对p1的修饰,所以p2还是int型
const限定符
-
动机:希望定义一些不能被改变值的变量。
初始化和const
-
const对象必须初始化,且不能被改变。
-
const变量默认不能被其他文件访问,非要访问,必须在指定const定义之前加extern。要想在多个文件中使用const变量共享,定义和声明都加const关键字即可。
const的引用
-
reference to const(对常量的引用):指向const对象的引用,如
const int ival=1; const int &refVal = ival;
,可以读取但不能修改refVal
。 -
临时量(temporary)对象:当编译器需要一个空间来暂存表达式的求值结果时,临时创建的一个未命名的对象。
-
对临时量的引用是非法行为。
指针和const
-
pointer to const(指向常量的指针):不能用于改变其所指对象的值, 如
const double pi = 3.14; const double *cptr = π
。 -
const pointer:指针本身是常量,也就是说指针固定指向该对象,(存放在指针中的地址不变,地址所对应的那个对象值可以修改)如
int i = 0; int *const ptr = &i;
顶层const
-
顶层const
:指针本身是个常量。 -
底层const
:指针指向的对象是个常量。拷贝时严格要求相同的底层const资格。
constexpr
和常量表达式(▲可选)
-
常量表达式:指值不会改变,且在编译过程中就能得到计算结果的表达式。
-
C++11
新标准规定,允许将变量声明为constexpr
类型以便由编译器来验证变量的值是否是一个常量的表达式。
处理类型
类型别名
-
传统别名:使用typedef来定义类型的同义词。
typedef double wages;
-
新标准别名:别名声明(alias declaration):
using SI = Sales_item;
(C++11)
// 对于复合类型(指针等)不能代回原式来进行理解 typedef char *pstring; // pstring是char*的别名 const pstring cstr = 0; // 指向char的常量指针 // 如改写为const char *cstr = 0;不正确,为指向const char的指针 // 辅助理解(可代回后加括号) // const pstring cstr = 0;代回后const (char *) cstr = 0; // const char *cstr = 0;即为(const char *) cstr = 0;
auto类型说明符 c++11
-
auto类型说明符:让编译器自动推断类型。
-
一条声明语句只能有一个数据类型,所以一个auto声明多个变量时只能相同的变量类型(包括复杂类型&和*)。
auto sz = 0, pi =3.14//错误
-
int i = 0, &r = i; auto a = r;
推断a
的类型是int
。 -
会忽略
顶层const
。 -
const int ci = 1; const auto f = ci;
推断类型是int
,如果希望是顶层const需要自己加const
decltype类型指示符
-
从表达式的类型推断出要定义的变量的类型。
-
decltype:选择并返回操作数的数据类型。
-
decltype(f()) sum = x;
推断sum
的类型是函数f
的返回类型。 -
不会忽略
顶层const
。 -
如果对变量加括号,编译器会将其认为是一个表达式,如int i-->(i),则decltype((i))得到结果为int&引用。
-
赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果 i 是 int,则表达式 i=x 的类型是 int&。
-
C++11
自定义数据结构
struct
尽量不要吧类定义和对象定义放在一起。如
struct Student{} xiaoming,xiaofang;
-
类可以以关键字
struct
开始,紧跟类名和类体。 -
类数据成员:类体定义类的成员。
-
C++11
:可以为类数据成员提供一个类内初始值(in-class initializer)。
编写自己的头文件
-
头文件通常包含哪些只能被定义一次的实体:类、
const
和constexpr
变量。
预处理器概述:
-
预处理器(preprocessor):确保头文件多次包含仍能安全工作。
-
当预处理器看到
#include
标记时,会用指定的头文件内容代替#include
-
头文件保护符(header guard):头文件保护符依赖于预处理变量的状态:已定义和未定义。
-
#indef
已定义时为真 -
#inndef
未定义时为真 -
头文件保护符的名称需要唯一,且保持全部大写。养成良好习惯,不论是否该头文件被包含,要加保护符。
-
#ifndef SALES_DATA_H //SALES_DATA_H未定义时为真 #define SALES_DATA_H strct Sale_data{ ... } #endif
第三章 字符串、向量和数组
using声明
-
使用某个命名空间:例如
using std::cin
表示使用命名空间std
中的名字cin
。 -
头文件中不应该包含
using
声明。这样使用了该头文件的源码也会使用这个声明,会带来风险。
string
-
标准库类型
string
表示可变长的字符序列。 -
#include <string>
,然后using std::string;
-
string对象:注意,不同于字符串字面值。
定义和初始化string对象
初始化string
对象的方式:
方式 | 解释 |
---|---|
string s1 | 默认初始化,s1 是个空字符串 |
string s2(s1) | s2 是s1 的副本 |
string s2 = s1 | 等价于s2(s1) ,s2 是s1 的副本 |
string s3("value") | s3 是字面值“value”的副本,除了字面值最后的那个空字符外 |
string s3 = "value" | 等价于s3("value") ,s3 是字面值"value"的副本 |
string s4(n, 'c') | 把s4 初始化为由连续n 个字符c 组成的串 |
-
拷贝初始化(copy initialization):使用等号
=
将一个已有的对象拷贝到正在创建的对象。 -
直接初始化(direct initialization):通过括号给对象赋值。
string对象上的操作
string
的操作:
操作 | 解释 |
---|---|
os << s | 将s 写到输出流os 当中,返回os |
is >> s | 从is 中读取字符串赋给s ,字符串以空白分割,返回is |
getline(is, s) | 从is 中读取一行赋给s ,返回is |
s.empty() | s 为空返回true ,否则返回false |
s.size() | 返回s 中字符的个数 |
s[n] | 返回s 中第n 个字符的引用,位置n 从0计起 |
s1+s2 | 返回s1 和s2 连接后的结果 |
s1=s2 | 用s2 的副本代替s1 中原来的字符 |
s1==s2 | 如果s1 和s2 中所含的字符完全一样,则它们相等;string 对象的相等性判断对字母的大小写敏感 |
s1!=s2 | 同上 |
< , <= , > , >= | 利用字符在字典中的顺序进行比较,且对字母的大小写敏感(对第一个不相同的位置进行比较) |
-
string io:
-
执行读操作
>>
:忽略掉开头的空白(包括空格、换行符和制表符),直到遇到下一处空白为止。 -
getline
:读取一整行,包括空白符。
-
-
s.size()
返回的时string::size_type
类型,记住是一个无符号类型的值,不要和int
混用 -
s1+s2
使用时,保证至少一侧是string类型。string s1 = "hello" + "world" // 错误,两侧均为字符串字面值
-
字符串字面值和string是不同的类型。
处理string对象中的字符
-
ctype.h vs. cctype:C++修改了c的标准库,名称为去掉
.h
,前面加c
。如c++版本为
cctype
,c版本为ctype.h
-
尽量使用c++版本的头文件,即
cctype
-
cctype
头文件中定义了一组标准函数:
函数 | 解释 |
---|---|
isalnum(c) | 当c 是字母或数字时为真 |
isalpha(c) | 当c 是字母时为真 |
iscntrl(c) | 当c 是控制字符时为真 |
isdigit(c) | 当c 是数字时为真 |
isgraph(c) | 当c 不是空格但可以打印时为真 |
islower(c) | 当c 是小写字母时为真 |
isprint(c) | 当c 是可打印字符时为真 |
ispunct(c) | 当c 是标点符号时为真 |
isspace(c) | 当c 是空白时为真(空格、横向制表符、纵向制表符、回车符、换行符、进纸符) |
isupper(c) | 当c 是大写字母时为真 |
isxdigit(c) | 当c 是十六进制数字时为真 |
tolower(c) | 当c 是大写字母,输出对应的小写字母;否则原样输出c |
toupper(c) | 当c 是小写字母,输出对应的大写字母;否则原样输出c |
-
遍历字符串:使用范围for(range for)语句:
for (auto c: str)
,或者for (auto &c: str)
使用引用直接改变字符串中的字符。 (C++11) -
str[x]
,[]输入参数为string::size_type
类型,给出int
整型也会自动转化为该类型
vector
-
vector是一个容器,也是一个类模板;
-
#include <vector>
然后using std::vector;
-
容器:包含其他对象。
-
类模板:本身不是类,但可以实例化instantiation出一个类。
vector
是一个模板,vector<int>
是一个类型。 -
通过将类型放在类模板名称后面的尖括号中来指定类型,如
vector<int> ivec
。
定义和初始化vector对象
初始化vector
对象的方法
方法 | 解释 |
---|---|
vector<T> v1 | v1 是一个空vector ,它潜在的元素是T 类型的,执行默认初始化 |
vector<T> v2(v1) | v2 中包含有v1 所有元素的副本 |
vector<T> v2 = v1 | 等价于v2(v1) ,v2 中包含v1 所有元素的副本 |
vector<T> v3(n, val) | v3 包含了n个重复的元素,每个元素的值都是val |
vector<T> v4(n) | v4 包含了n个重复地执行了值初始化的对象 |
vector<T> v5{a, b, c...} | v5 包含了初始值个数的元素,每个元素被赋予相应的初始值 |
vector<T> v5={a, b, c...} | 等价于v5{a, b, c...} |
-
列表初始化:
vector<string> v{"a", "an", "the"};
(C++11)
向vector对象中添加元素
-
v.push_back(e)
在尾部增加元素。
其他vector操作
vector
支持的操作:
操作 | 解释 |
---|---|
v.emtpy() | 如果v 不含有任何元素,返回真;否则返回假 |
v.size() | 返回v 中元素的个数 |
v.push_back(t) | 向v 的尾端添加一个值为t 的元素 |
v[n] | 返回v 中第n 个位置上元素的引用 |
v1 = v2 | 用v2 中的元素拷贝替换v1 中的元素 |
v1 = {a,b,c...} | 用列表中元素的拷贝替换v1 中的元素 |
v1 == v2 | v1 和v2 相等当且仅当它们的元素数量相同且对应位置的元素值都相同 |
v1 != v2 | 同上 |
< ,<= ,> , >= | 以字典顺序进行比较 |
-
范围
for
语句内不应该改变其遍历序列的大小。 -
vector
对象(以及string
对象)的下标运算符,只能对确知已存在的元素执行下标操作,不能用于添加元素。
迭代器iterator
-
所有标准库容器都可以使用迭代器。
-
类似于指针类型,迭代器也提供了对对象的间接访问。
使用迭代器
-
vector<int>::iterator iter
。 -
auto b = v.begin();
返回指向第一个元素的迭代器。 -
auto e = v.end();
返回指向最后一个元素的下一个(哨兵,尾后,one past the end)的迭代器(off the end)。 -
如果容器为空,
begin()
和end()
返回的是同一个迭代器,都是尾后迭代器。 -
使用解引用符
*
访问迭代器指向的元素。 -
养成使用迭代器和
!=
的习惯(泛型编程)。 -
容器:可以包含其他对象;但所有的对象必须类型相同。
-
迭代器(iterator):每种标准容器都有自己的迭代器。
C++
倾向于用迭代器而不是下标遍历元素。 -
const_iterator:只能读取容器内元素不能改变。
-
箭头运算符: 解引用 + 成员访问,
it->mem
等价于(*it).mem
-
谨记:但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。
标准容器迭代器的运算符:
运算符 | 解释 |
---|---|
*iter | 返回迭代器iter 所指向的元素的引用 |
iter->mem | 等价于(*iter).mem |
++iter | 令iter 指示容器中的下一个元素 |
--iter | 令iter 指示容器中的上一个元素 |
iter1 == iter2 | 判断两个迭代器是否相等 |
迭代器运算
vector
和string
迭代器支持的运算:
运算符 | 解释 |
---|---|
iter + n | 迭代器加上一个整数值仍得到一个迭代器,迭代器指示的新位置和原来相比向前移动了若干个元素。结果迭代器或者指示容器内的一个元素,或者指示容器尾元素的下一位置。 |
iter - n | 迭代器减去一个证书仍得到一个迭代器,迭代器指示的新位置比原来向后移动了若干个元素。结果迭代器或者指向容器内的一个元素,或者指示容器尾元素的下一位置。 |
iter1 += n | 迭代器加法的复合赋值语句,将iter1 加n的结果赋给iter1 |
iter1 -= n | 迭代器减法的复合赋值语句,将iter2 减n的加过赋给iter1 |
iter1 - iter2 | 两个迭代器相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前移动差值个元素后得到左侧的迭代器。参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一位置。 |
> 、>= 、< 、<= | 迭代器的关系运算符,如果某迭代器 |
-
difference_type:保证足够大以存储任何两个迭代器对象间的距离,可正可负。
数组
-
相当于vector的低级版,长度固定。
定义和初始化内置数组
-
初始化:
char input_buffer[buffer_size];
,长度必须是const表达式,或者不写,让编译器自己推断。 -
数组不允许直接赋值给另一个数组。
访问数组元素
-
数组下标的类型:
size_t
。 -
字符数组的特殊性:结尾处有一个空字符,如
char a[] = "hello";
。 -
用数组初始化
vector
:int a[] = {1,2,3,4,5}; vector<int> v(begin(a), end(a));
。
数组和指针
-
使用数组时,编译器一般会把它转换成指针。
-
标准库类型限定使用的下标必须是无符号类型,而内置的下标可以处理负值。
-
指针访问数组:在表达式中使用数组名时,名字会自动转换成指向数组的第一个元素的指针。
C风格字符串
-
从C继承来的字符串。
-
用空字符结束(
\0
)。 -
对大多数应用来说,使用标准库
string
比使用C风格字符串更安全、更高效。 -
获取
string
中的cstring
:const char *str = s.c_str();
。
C标准库String函数,定义在<cstring>
中:
函数 | 介绍 |
---|---|
strlen(p) | 返回p 的长度,空字符不计算在内 |
strcmp(p1, p2) | 比较p1 和p2 的相等性。如果p1==p2 ,返回0;如果p1>p2 ,返回一个正值;如果p1<p2 ,返回一个负值。 |
strcat(p1, p2) | 将p2 附加到p1 之后,返回p1 |
strcpy(p1, p2) | 将p2 拷贝给p1 ,返回p1 |
尽量使用vector和迭代器,少用数组
多维数组
-
多维数组的初始化:
int ia[3][4] = {{0,1,2,3}, ...}
。 -
使用范围for语句时,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。
指针vs引用
-
引用总是指向某个对象,定义引用时没有初始化是错的。
-
给引用赋值,修改的是该引用所关联的对象的值,而不是让引用和另一个对象相关联。
指向指针的指针
-
定义:
int **ppi = π
-
解引用:
**ppi
动态数组
-
使用
new
和delete
表达和c中malloc
和free
类似的功能,即在堆(自由存储区)中分配存储空间。 -
定义:
int *pia = new int[10];
10可以被一个变量替代。 -
释放:
delete [] pia;
,注意不要忘记[]
。
第四章 表达式
表达式基础
-
运算对象转换:小整数类型会被提升为较大的整数类型
-
重载运算符:当运算符作用在类类型的运算对象时,用户可以自行定义其含义。
-
左值和右值:
-
C中原意:左值可以在表达式左边,右值不能。
-
C++
:当一个对象被用作右值的时候,用的是对象的值(内容); -
被用做左值时,用的是对象的身份(在内存中的位置)。
-
-
求值顺序:
int i = f1() + f2()
-
先计算
f1() + f2()
,再计算int i = f1() + f2()
。但是f1和f2的计算先后不确定 -
但是,如果f1、f2都对同一对象进行了修改,因为顺序不确定,所以会编译出错,显示未定义
-
算术运算符
-
溢出:当计算的结果超出该类型所能表示的范围时就会产生溢出。
-
bool类型不应该参与计算
bool b=true; bool b2=-b; //仍然为true //b为true,提升为对应int=1,-b=-1 //b2=-1≠0,所以b2仍未true
-
取余运算m%n,结果符号与m相同
逻辑运算符
-
短路求值:逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值。先左再右
-
小技巧,声明为引用类型可以避免对元素的拷贝,如下,如string特别大时可以节省大量时间。
vector<string> text; for(const auto &s: text){ cout<<s; }
赋值运算符
-
赋值运算的返回结果时它的左侧运算对象,且是一个左值。类型也就是左侧对象的类型。
-
如果赋值运算的左右侧运算对象类型不同,则右侧运算对象将转换成左侧运算对象的类型。
-
赋值运算符满足右结合律,这点和其他二元运算符不一样。
ival = jval = 0;
等价于ival = (jval = 0);
-
赋值运算优先级比较低,使用其当条件时应该加括号。
-
复合赋值运算符,复合运算符只求值一次,普通运算符求值两次。(对性能有一点点点点影响) 任意复合运算符op等价于
a = a op b;
递增递减运算符
-
前置版本
j = ++i
,先加一后赋值 -
后置版本
j = i++
,先赋值后加一
优先使用前置版本,后置多一步储存原始值。(除非需要变化前的值)
混用解引用和递增运算符
*iter++
等价于*(iter++)
,递增优先级较高
auto iter = vi.begin(); while (iter!=vi.end()&&*iter>=0) cout<<*iter++<<endl; // 输出当前值,指针向前移1
简介是一种美德,追求简洁能降低程序出错可能性
成员访问运算符
ptr->mem
等价于(*ptr).mem
注意.
运算符优先级大于*
,所以记得加括号
条件运算符
-
条件运算符(
?:
)允许我们把简单的if-else
逻辑嵌入到单个表达式中去,按照如下形式:cond? expr1: expr2
-
可以嵌套使用,右结合律,从右向左顺序组合
-
finalgrade = (grade > 90) ? "high pass" : (grade < 60) ? "fail" : "pass"; //等价于 finalgrade = (grade > 90) ? "high pass" : ((grade < 60) ? "fail" : "pass");
-
-
输出表达式使用条件运算符记得加括号,条件运算符优先级太低。
位运算符
用于检查和设置二进制位的功能。
-
位运算符是作用于整数类型的运算对象。
-
二进制位向左移(
<<
)或者向右移(>>
),移出边界外的位就被舍弃掉了。 -
位取反(
~
)(逐位求反)、与(&
)、或(|
)、异或(^
)
有符号数负值可能移位后变号,所以强烈建议位运算符仅用于无符号数。
应用:
unsigned long quiz1 = 0; // 每一位代表一个学生是否通过考试 1UL << 12; // 代表第12个学生通过 quiz1 |= (1UL << 12); // 将第12个学生置为已通过 quiz1 &= ~(1UL << 12); // 将第12个学生修改为未通过 bool stu12 = quiz1 & (1UL << 12); // 判断第12个学生是否通过
位运算符使用较少,但是重载cout、cin大家都用过
位运算符满足左结合律,优先级介于中间,使用时尽量加括号。
sizeof运算符
-
返回一条表达式或一个类型名字所占的字节数。
-
返回的类型是
size_t
的常量表达式。 -
sizeof
并不实际计算其运算对象的值。 -
两种形式:
-
sizeof (type)
,给出类型名 -
sizeof expr
,给出表达式
-
-
可用sizeof返回数组的大小
int ia[10]; // sizeof(ia)返回整个数组所占空间的大小 // sizeof(ia)/sizeof(*ia)返回数组的大小 constexpr size_t sz = sizeof(ia)/sizeof(*ia); int arr[sz];
逗号运算符
从左向右依次求值。
左侧求值结果丢弃,逗号运算符结果是右侧表达式的值。
类型转换
隐式类型转换
设计为尽可能避免损失精度,即转换为更精细类型。
-
比
int
类型小的整数值先提升为较大的整数类型。 -
条件中,非布尔转换成布尔。
-
初始化中,初始值转换成变量的类型。
-
算术运算或者关系运算的运算对象有多种类型,要转换成同一种类型。
-
函数调用时也会有转换。
算术转换
整型提升
-
常见的char、bool、short能存在int就会转换成int,否则提升为
unsigned int
-
wchar_t,char16_t,char32_t
提升为整型中int,long,long long ……
最小的,且能容纳原类型所有可能值的类型。
其他转换
p143
显式类型转换(尽量避免)
-
static_cast:任何明确定义的类型转换,只要不包含底层const,都可以使用。
double slope = static_cast<double>(j);
-
dynamic_cast:支持运行时类型识别。
-
const_cast:只能改变运算对象的底层const,一般可用于去除const性质。
const char *pc; char *p = const_cast<char*>(pc)
只有其可以改变常量属性
-
reinterpret_cast:通常为运算对象的位模式提供低层次上的重新解释。
旧式强制类型转换
type expr
运算符优先级表
p147
第五章 语句
简单语句
-
表达式语句:一个表达式末尾加上分号,就变成了表达式语句。
-
空语句:只有一个单独的分号。
-
复合语句(块):用花括号
{}
包裹起来的语句和声明的序列。一个块就是一个作用域。
条件语句
-
悬垂else(dangling else):用来描述在嵌套的
if else
语句中,如果if
比else
多时如何处理的问题。C++使用的方法是else
匹配最近没有配对的if
。
迭代语句
-
while:当不确定到底要迭代多少次时,使用
while
循环比较合适,比如读取输入的内容。 -
for:
for
语句可以省略掉init-statement
,condition
和expression
的任何一个;甚至全部。 -
范围for:
for (declaration: expression) statement
跳转语句
-
break:
break
语句负责终止离它最近的while
、do while
、for
或者switch
语句,并从这些语句之后的第一条语句开始继续执行。 -
continue:终止最近的循环中的当前迭代并立即开始下一次迭代。只能在
while
、do while
、for
循环的内部。
try语句块和异常处理
-
throw表达式:异常检测部分使用
throw
表达式来表示它遇到了无法处理的问题。我们说throw
引发raise
了异常。 -
try语句块:以
try
关键词开始,以一个或多个catch
字句结束。try
语句块中的代码抛出的异常通常会被某个catch
捕获并处理。catch
子句也被称为异常处理代码。 -
异常类:用于在
throw
表达式和相关的catch
子句之间传递异常的具体信息。
第六章 函数
函数基础
-
函数定义:包括返回类型、函数名字和0个或者多个形参(parameter)组成的列表和函数体。
-
调用运算符:调用运算符的形式是一对圆括号
()
,作用于一个表达式,该表达式是函数或者指向函数的指针。 -
圆括号内是用逗号隔开的实参(argument)列表。
-
函数调用过程:
-
1.主调函数(calling function)的执行被中断。
-
2.被调函数(called function)开始执行。
-
-
形参和实参:形参和实参的个数和类型必须匹配上。
-
返回类型:
void
表示函数不返回任何值。函数的返回类型不能是数组类型或者函数类型,但可以是指向数组或者函数的指针。 -
名字:名字的作用于是程序文本的一部分,名字在其中可见。
局部对象
-
生命周期:对象的生命周期是程序执行过程中该对象存在的一段时间。
-
局部变量(local variable):形参和函数体内部定义的变量统称为局部变量。它对函数而言是局部的,对函数外部而言是隐藏的。
-
自动对象:只存在于块执行期间的对象。当块的执行结束后,它的值就变成未定义的了。
-
局部静态对象:
static
类型的局部变量,生命周期贯穿函数调用前后。
函数声明
-
函数声明:函数的声明和定义唯一的区别是声明无需函数体,用一个分号替代。函数声明主要用于描述函数的接口,也称函数原型。
-
在头文件中进行函数声明:建议变量在头文件中声明;在源文件中定义。
-
分离编译:
CC a.cc b.cc
直接编译生成可执行文件;CC -c a.cc b.cc
编译生成对象代码a.o b.o
;CC a.o b.o
编译生成可执行文件。
参数传递
-
形参初始化的机理和变量初始化一样。
-
引用传递(passed by reference):又称传引用调用(called by reference),指形参是引用类型,引用形参是它对应的实参的别名。
-
值传递(passed by value):又称传值调用(called by value),指实参的值是通过拷贝传递给形参。
传值参数
-
当初始化一个非引用类型的变量时,初始值被拷贝给变量。
-
函数对形参做的所有操作都不会影响实参。
-
指针形参:常用在C中,
C++
建议使用引用类型的形参代替指针。
传引用参数
-
通过使用引用形参,允许函数改变一个或多个实参的值。
-
引用形参直接关联到绑定的对象,而非对象的副本。
-
使用引用形参可以用于返回额外的信息。
-
经常用引用形参来避免不必要的复制。
-
void swap(int &v1, int &v2)
-
如果无需改变引用形参的值,最好将其声明为常量引用。
const形参和实参
-
形参的顶层
const
被忽略。void func(const int i);
调用时既可以传入const int
也可以传入int
。 -
我们可以使用非常量初始化一个底层
const
对象,但是反过来不行。 -
在函数中,不能改变实参的局部副本。
-
尽量使用常量引用。
数组形参
-
当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
-
要注意数组的实际长度,不能越界。
main处理命令行选项
-
int main(int argc, char *argv[]){...}
-
第一个形参代表参数的个数;第二个形参是参数C风格字符串数组。
可变形参
initializer_list
提供的操作(C++11
):
操作 | 解释 |
---|---|
initializer_list<T> lst; | 默认初始化;T 类型元素的空列表 |
initializer_list<T> lst{a,b,c...}; | lst 的元素数量和初始值一样多;lst 的元素是对应初始值的副本;列表中的元素是const 。 |
lst2(lst) | 拷贝或赋值一个initializer_list 对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素。 |
lst2 = lst | 同上 |
lst.size() | 列表中的元素数量 |
lst.begin() | 返回指向lst 中首元素的指针 |
lst.end() | 返回指向lst 中微元素下一位置的指针 |
initializer_list
使用demo:
void err_msg(ErrCode e, initializer_list<string> il){ cout << e.msg << endl; for (auto bed = il.begin(); beg != il.end(); ++ beg) cout << *beg << " "; cout << endl; } err_msg(ErrCode(0), {"functionX", "okay});
-
所有实参类型相同,可以使用
initializer_list
的标准库类型。 -
实参类型不同,可以使用
可变参数模板
。 -
省略形参符:
...
,便于C++
访问某些C代码,这些C代码使用了varargs
的C标准功能。
返回类型和return语句
无返回值函数
没有返回值的 return
语句只能用在返回类型是 void
的函数中,返回 void
的函数不要求非得有 return
语句。
有返回值函数
-
return
语句的返回值的类型必须和函数的返回类型相同,或者能够隐式地转换成函数的返回类型。 -
值的返回:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。
-
不要返回局部对象的引用或指针。
-
引用返回左值:函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值;其他返回类型得到右值。
-
列表初始化返回值:函数可以返回花括号包围的值的列表。(
C++11
) -
主函数main的返回值:如果结尾没有
return
,编译器将隐式地插入一条返回0的return
语句。返回0代表执行成功。
返回数组指针
-
Type (*function (parameter_list))[dimension]
-
使用类型别名:
typedef int arrT[10];
或者using arrT = int[10;]
,然后arrT* func() {...}
-
使用
decltype
:decltype(odd) *arrPtr(int i) {...}
-
尾置返回类型: 在形参列表后面以一个
->
开始:auto func(int i) -> int(*)[10]
(C++11
)
函数重载
-
重载:如果同一作用域内几个函数名字相同但形参列表不同,我们称之为重载(overload)函数。
-
main
函数不能重载。 -
重载和const形参:
-
一个有顶层const的形参和没有它的函数无法区分。
Record lookup(Phone* const)
和Record lookup(Phone*)
无法区分。 -
相反,是否有某个底层const形参可以区分。
Record lookup(Account*)
和Record lookup(const Account*)
可以区分。
-
-
重载和作用域:若在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体,在不同的作用域中无法重载函数名。
特殊用途语言特性
默认实参
-
string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');
-
一旦某个形参被赋予了默认值,那么它之后的形参都必须要有默认值。
内联(inline)函数
-
普通函数的缺点:调用函数比求解等价表达式要慢得多。
-
inline
函数可以避免函数调用的开销,可以让编译器在编译时内联地展开该函数。 -
inline
函数应该在头文件中定义。
constexpr函数
-
指能用于常量表达式的函数。
-
constexpr int new_sz() {return 42;}
-
函数的返回类型及所有形参类型都要是字面值类型。
-
constexpr
函数应该在头文件中定义。
调试帮助
-
assert
预处理宏(preprocessor macro):assert(expr);
开关调试状态:
CC -D NDEBUG main.c
可以定义这个变量NDEBUG
。
void print(){ #ifndef NDEBUG cerr << __func__ << "..." << endl; #endif }
函数匹配
-
重载函数匹配的三个步骤:1.候选函数;2.可行函数;3.寻找最佳匹配。
-
候选函数:选定本次调用对应的重载函数集,集合中的函数称为候选函数(candidate function)。
-
可行函数:考察本次调用提供的实参,选出可以被这组实参调用的函数,新选出的函数称为可行函数(viable function)。
-
寻找最佳匹配:基本思想:实参类型和形参类型越接近,它们匹配地越好。
函数指针
-
函数指针:是指向函数的指针。
-
bool (*pf)(const string &, const string &);
注:两端的括号不可少。 -
函数指针形参:
-
形参中使用函数定义或者函数指针定义效果一样。
-
使用类型别名或者
decltype
。
-
-
返回指向函数的指针:1.类型别名;2.尾置返回类型。