参考资料
Computer Architecture A Quantitative Approach pdf 翻译
鱼C工作室 C/C++/Python/Wed/数据结构和算法
新特征 C++11/14/17 concepts and code snippets
A Detailed Cplusplus Concurrency Tutorial 《C++ 并发编程指南》
基于C++11新标准的并发和多线程编程深度指南 C++ Concurrency In Action
甲骨文公司编辑器Oracle Solaris Studio 12.4 Information Library (简体中文) c/cpp用户指南 数值计算指南 代码分析器 性能分析器 线程分析器
感谢支持
Git
git clone --recurse-submodules https://github.com/xxxxx.git
注意 clone 的时候一定要带 --recurse-submodules 这个参数,否则会下载不完整。
包含内容:
1. c
2. c++
3. python
4. 汇编语言
5. 数据机构和算法 面试
6. 操作系统os
7. 单片机stm32 arduino Ti-msp430 树莓派 px4 arm
8. 数据挖掘
9. 人机工程学
10. 计算机科学
学习C++,应该循序渐进的看哪些书?
阶段 1
《Essential C++》
这是一本内容不多但很实用的C++入门书籍,强调快速上手与理解C++编程。
本书主要围绕一系列逐渐复杂的程序问题,以及用以解决这些问题的语言特性展开讲解。
你不只学到C++的函数和结构,也会学习到它们的设计目的和基本原理。
《C++ Primer》
本书对C++基本概念、技术、以及现代C++编程风格进行了全面而且权威的阐述,是C++初学者的最佳指南;
本书可以帮助你编写实用的程序,而无需首先精通每个语言细节。
对于中高级程序员,本书也是不可或缺的参考书。
阶段 2
《Effective C++》和《More effective C++》作者是Scott Meyers。
你应该熟读它们,并清楚地理解每个项目。
该书围绕55条准则,每一条都介绍了一个可让你写出更好的C++程序代码的方法,并以特别设计过的例子详加讨论。
《Exceptional C++(C++编程剖析)》和《More exceptional C++》
这两本书中都包含了40个C++编程问题,这些问题会让你磨练自己的技能,最终成为优秀的C++程序员。
这些问题是Herb Sutter精心挑选,与ISO/ANSI C++官方标准相一致,
帮助程序员在设计、架构和编码过程中保持良好的风格,从而使编写的C++软件更健壮、更高效。
阶段 3
《Inside the C++ object model(深度探索C++对象模型)》
本书专注于C++面向对象程序设计的底层机制,
包括结构式语意、临时性对象的生成、封装、继承,以及虚拟——虚拟函数和虚拟继承,
帮助你理解程序的底层实现,以便写出更高效的代码。
《The design and evolution of C++(C++语言的设计与演化)》
本书作者也是C++语言的设计者Bjarne Stroustrup,作者在书中综合性地介绍了C++的发展历史,
C++中各种重要机制的本质意义和设计背景,这些机制的基本用途和使用方法,
讨论了C++所适合的应用领域及其未来的发展前景,既没有忽略关键性的详情,又没有过多地陷入技术细节。
阶段 4
《The C++ standard library(C++标准程序库)》
这是标准模板库字典,你可以在本书中找到STL相关的一切知识。
本书焦点放在标准模板库、检查容器、迭代器、函数对象和STL算法上。
每一个元素都有深刻的呈现,包括其介绍、设计、运用实例、
细节解说、陷阱、意想不到的危险,以及相关类别和函数等。
《Effective STL》
这是Scott Meyers的第三本C++专著,也是学习STL最权威的书籍。
作者对书中的50个指导方针都作了详尽的分析,并配以示例。
通过这些规则,C++开发者可以最大限度地使用STL。
《Generic programming and the STL(泛型编程与STL)》
本书阐述了泛型程序设计的核心理念:concepts(概念)、modeling(模型)和refinement(改善),
并为你展示这些观念如何导出STL的基础概念:iterators(迭代器)、
containers(容器)和function objects(函数对象)。
按照本书所述,你可以把STL想象成一个由concepts组成的library,你将学习到STL正式结构并理解其强大的优势。
阶段 5
《Exceptional C++ style》
作者为Herb Sutter。本书同样提出了40个C++风格相关的问题
,对一些至关重要的C++细节和相互关系提出了新的见解,
为当今的关键C++编程技术(如泛型编程、STL、异常安全等)提供了新的策略,
帮助开发者在开销与功能之间、优雅与可维护性之间、灵活性与过分灵活之间寻找完美的平衡点。
《C++ template》
这是一本关于C++模板的完整的参考手册和教程,它强调模板的使用实践,包含了现实世界中的例子。
每个C++程序员都应该好好读一读这本书。
《Modern C++ design(现代C++设计)》
作者Andrei Alexandrescu为C++程序员打开了一个新的局面。
本书提供了一些针对软件设计的前沿方法,如联合设计模式、泛型编程,
使程序员可以编写有表现力的、灵活的、高度可重用的代码。
《Thinking in C++(C++编程思想)》
C++ 领域权威著作,介绍了C++实用的编程技术和最佳的实践方法。
值得学习的开源代码
1.Webbench
Webbench是一个在Linux下使用的非常简单的网站压测工具。
它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,
最多可以模拟3万个并发连接去测试网站的负载能力。
Webbench使用C语言编写, 代码实在太简洁,源码加起来不到600行。
项目主页: http://home.tiscali.cz/~cz210552/webbench.html
2. Tinyhttpd
tinyhttpd是一个超轻量型Http Server,使用C语言开发,全部代码只有502行(包括注释),
附带一个简单的Client,可以通过阅读这段代码理解一个 Http Server 的本质。
项目主页: http://sourceforge.net/projects/tinyhttpd/
3. cJSON
cJSON是C语言中的一个JSON编解码器,非常轻量级,C文件只有500多行,速度也非常理想。
cJSON也存在几个弱点,虽然功能不是非常强大,但cJSON的小身板和速度是最值得赞赏的。
其代码被非常好地维护着,结构也简单易懂,可以作为一个非常好的C语言项目进行学习。
项目主页: http://sourceforge.net/projects/cjson/
4. CMockery
cmockery是google发布的用于C单元测试的一个轻量级的框架。
它很小巧,对其他开源包没有依赖,对被测试代码侵入性小。
cmockery的源代码行数不到3K,你阅读一下will_return和mock的源代码就一目了然了。
主要特点:
免费且开源,google提供技术支持;
轻量级的框架,使测试更加快速简单;
避免使用复杂的编译器特性,对老版本的编译器来讲,兼容性好;
并不强制要求待测代码必须依赖C99标准,这一特性对许多嵌入式系统的开发很有用
项目主页: http://code.google.com/p/cmockery/downloads/list
5. Libev
libev是一个开源的事件驱动库,基于epoll,kqueue等OS提供的基础设施。
其以高效出名,它可以将IO事件,定时器,和信号统一起来,统一放在事件处理这一套框架下处理。
基于Reactor模式,效率较高,并且代码精简(4.15版本8000多行),是学习事件驱动编程的很好的资源。
项目主页: http://software.schmorp.de/pkg/libev.html
6. Memcached
Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。
它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提供动态数据库驱动网站的速度。
Memcached 基于一个存储键/值对的 hashmap。
Memcached-1.4.7的代码量还是可以接受的,只有10K行左右。
项目主页: http://memcached.org/
7. Lua
Lua很棒,Lua是巴西人发明的,这些都令我不爽,但是还不至于脸红,最多眼红。
让我脸红的是Lua的源代码,百分之一百的ANSI C,一点都不掺杂。
在任何支持ANSI C编译器的平台上都可以轻松编译通过。我试过,真是一点废话都没有。
Lua的代码数量足够小,5.1.4仅仅1.5W行,去掉空白行和注释估计能到1W行。
项目主页: http://www.lua.org/
8. SQLite
SQLite是一个开源的嵌入式关系数据库,实现自包容、零配置、支持事务的SQL数据库引擎。
其特点是高度便携、使用方便、结构紧凑、高效、可靠。足够小,大致3万行C代码,250K。
项目主页: http://www.sqlite.org/ 。
9. UNIX v6
UNIX V6 的内核源代码包括设备驱动程序在内 约有1 万行,这个数量的源代码,初学者是能够充分理解的。
有一种说法是一个人所能理解的代码量上限为1 万行,UNIX V6的内核源代码从数量上看正好在这个范围之内。
看到这里,大家是不是也有“如果只有1万行的话没准儿我也能学会”的想法呢?
另一方面,最近的操作系统,例如Linux 最新版的内核源代码据说超过了1000 万行。
就算不是初学者,想完全理解全部代码基本上也是不可能的。
项目主页: http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6
10. NETBSD
NetBSD是一个免费的,具有高度移植性的 UNIX-like 操作系统,
是现行可移植平台最多的操作系统,可以在许多平台上执行,
从 64bit alpha 服务器到手持设备和嵌入式设备。
NetBSD计划的口号是:”Of course it runs NetBSD”。
它设计简洁,代码规范,拥有众多先进特性,使得它在业界和学术界广受好评。
由于简洁的设计和先进的特征,使得它在生产和研究方面,
都有卓越的表现,而且它也有受使用者支持的完整的源代码。
许多程序都可以很容易地通过NetBSD Packages Collection获得。
项目主页: http://www.netbsd.org/
0 个类型结构大小 sizeof
3. 语法:
sizeof有三种语法形式,如下:
1) sizeof( object ); // sizeof( 对象 );
2) sizeof( type_name ); // sizeof( 类型 );
3) sizeof object; // sizeof 对象;
2. 指针变量的sizeof 4/8
与计算机类型有关,32为计算机,地址长度为4字节
64位的计算机,地长度为 8字节
这里的指针包括所有类型的指针:
字符指针、整形指针、字符串指针、指针的指针、函数指针、数组指针等。
3.数组的sizeof
数组的sizeof值等于数组所占用的内存字节数,如:
char a1[] = "abc";
int a2[3];
sizeof( a1 ); // 结果为4,字符串末尾还存在一个NULL终止符
sizeof( a2 ); // 结果为3*4=12(依赖于int,这里int为4字节)
// 这里注意 &a2和a2的值是相等的,都是a2[0]的地址
// 但是 &a2 的类型为 int *[10]
// 而a2的类型为 int* p
//数组元素数量求取
int c1 = sizeof( a1 ) / sizeof( char ); // 总长度/单个元素的长度(知道元素类型)
int c2 = sizeof( a1 ) / sizeof( a1[0] ); // 总长度/第一个元素的长度(不知道元素类型)
4. sizeof进行结构体大小的判断
需要看编译器是几个字节对齐 的,一般为4字节对齐
typedef struct
{
int a; // 占据第一个4字节
char b; // 占据第二个4字节
}A_t;
typedef struct
{
int a; // 占据第一个4字节
char b;// 占据第二个4字节中的第一个字节
char c;// 占据第二个4字节中的第二个字节
}B_t;
typedef struct
{
char a;// 占据第一个4字节的第一个字节
int b; // 占据第二个4字节
char c;// 占据第三个4字节的第一个字节
}C_t;
1. 编程技巧 来自 effective stl
a)浮点数判等
判断两个浮点数a和b是否相等时,不要使用 a==b ,
应该判断两者的绝对值之差fabs(a-b)是否小于一个阈值 ,如1e-9
if(fabs(a-b) < 1e-9)
b) char类型用作数组下标需要注意的问题
应该先将char 强制转换为 unsigned char后在用作下标。
char index_;
unsigned char index = (unsigned char)index_;
c) 向量vector 和 字符串string 优先于 动态分配的数组new[] delete[]
vector 和 string 定义的对象 会自动 构造和析构,不用担心内存泄漏的问题
使用new[]分配的动态数组,需要配合 delete[]类释放会造成内存,否者会造成内存泄漏的问题
例如 定义一个二维数组,指针的指针
自己用new实现:
int** arr_pp new int* [row_num];// 定义一个存储指针的数组的指针 行数
for(i = 0; i < row_num; ++i)
arr_pp[i] = new int[col_num];// 一个一个new 指针每一行是一个行向量的指针
用 vector实现,一行代码搞定,还不到担心内存泄漏的问题
vector<vector<int>> v_i2(row_num,vector<int>(col_num,0));//初始化为一个0矩阵
vector<int> m;
m.reserve(1000);//提前保留1000的内存
//使用 reserve 避免不必要的重新分配
C++ primer5 语言学习记录
复合类型
引用 & 左值引用lvalue reference 别名; int val; &refval = val; 右值引用 rvalue reference
int val = 1024;//整形变量
int &refVal = val;// 整形引用 refVal 指向 val ,是val的另一个名字,
// 引用必须被初始化,引用不是对象,只是为所存在的对象起的一个别名。
refVal = 2; // 把2 赋值给refVal 也就是 赋值给了 val
int i = refVal; // 相当于 i = val;
int &refVal2 = 10;// 错误,非常量引用只能绑定(bind)在对象向,
// 不能与字面值或某个表达式的计算结果绑定在一起, 且引用的类型 必须和 对象的类型一致
const int &rci = 10;// 常量引用可以绑定到 字面值 非常量 一般表达式
double dval = 3.14;// 浮点数
int &refVal3 = dval; // 错误,引用的类型 和 对象 必须一致
指针 pointer * 指向 point to 另外一个对象(其存储地址)
int ival = 42; // 整数
int *p = &ival; // 指针定义 p存放 ival对象存放的地址,也即是 p 指向 变量 ival的指针 &ival 为取的 ival变量的 存储地址
double dval = 3.14; // 浮点数
double *pd = &dval; // double 类型 指针
double *pd2 = pd; // pd存放 的是 dval存放的地址
int *pi = pd; // 错误, 指针pi 的类型 和 右边指针对象 类型 不匹配
pi = &dval; // 错误, 视图把 double 类型对象的地址 给 int类型的指针 错误 左右两边类型 必须匹配
利用指针访问对象 取地址内存储的值 解引用符 来访问向
int ival = 42; //整形 变量
int *p = &ival;//指针变量定义初始化, p 存放着 变量ival 在内存中的地址
std::cout << *p;// 表达式里 *p 为 解引用 取的p存放的地址 指向的值
int &ref_i = ival; // 引用声明定义, ref_i 是一个引用
int *p; //指针声明定义,p是一个指针
p = &ival;// 表达式 p为指针(地址),&val 为取的 val 地址
*p = ival; //表达式 *p 相当于 ival *是一个解引用
int &ref_i2 = *p;// &ref_i2 为引用定义 *p 相当于 ival, *是一个解引用
空指针
int *p1 = nullptr;// 等价于 int *p1 = 0; 空指针 p1 不指向 任何 对象
int *p2 = 0; // 空指针 等价于 int *p1 = 0;
int *p3 = NULL;// 空指针 等价于 int *p1 = 0;
int zero = 0;
int *p4 = zero;// 错误,不能把 int 变量直接赋值 给指针
int i = 42;
int *pi1 = 0; // pi1 被初始化为空指针,但没有指向 任何对象
int *pi2 = &i;// pi2 被初始化,存有 i 的地址
int *pi3;// 定义 未初始化
pi3 = pi2;// pi3 和 pi2指向同一个 对象 i
pi2 = 0; // 现在 pi2 被赋值为 空指针 不指向任何对象 指针被改变, 指向的对象不变
pi2 = &i;// 现在 pi2 又指向 i
*pi2 = 0;// 指针pi2 没变 , *pi2 被改变 即pi2指向的对象 i 发生了变化
使用指针作为条件 为空指针时,逻辑值为 False,其他指向一个对象的指针(非0指针) 逻辑值 均为 true
相等 == 操作符 不等 != 操作符
void* 类型的指针 可一指向 任意类型 的 地址 , 可一存放任意类型的 地址,
用作函数参数,函数体内使用时需要,转化成实际传入类型的指针
double obj = 3.14, *pd = &obj; // double 类型 的变量 double类型的指针
void *pv = &obj; // obj 可以是任意类型的对象
pv = pd; // pv 可一存放任意类型的指针(地址)
double *pd = (double*)(pv);// void* 类型的指针 强制转化成 double* 类型的指针
定义多个变量
int i = 1024, *p = &i, &r = i;//i是整形变量 p是一个整形指针 r是一个整形引用
int* p1, p2;// * 仅仅修饰 p1 ,即p1是 指向整形的指针 p2是整形变量
int *p1, *p2;// p1 和 p2 都是指向 整形的指针
指向指针的指针
int ival = 1024;
int *pi = &ival; //指向整形变量的指针
int **ppi = π //指向整形指针的指针 ppi ---> pi ------> ival
std::cout << ival << "\n"// 直接输出 变量值
<< *pi << "\n"// 1次解引用 得到变量值
<< **ppi<< "\n";//2次解引用 的到变量值
指向指针的 引用 指针对象的别名 引用不是对象 不存在 指向引用的指针
int i = 42;//整形对象
int *p;//指向整形类型的指针
int *&r_p = p;//引用r_p的类型为 整形的指针引用int*&,r_p 是指针p的一个别名
//从右向左 离变量最近的符号为 & 即为 引用,* 引用的为 指针类型
r_p = &i;// 相当于 p = &i p 指向 i
*r_p = 0;// 相当于 *p = 0 即 i =0 改变了指针指向对象的值
// 这里 *p *解引用指针的类型其实相当于 原变量的别名 引用
const 限定符
修饰变量后,可以防止变量被修改
const int bufSize = 512;//初始化常量 一旦创建后就不能被修改
bufSize = 1024;// 错误,常量不能被赋值
const int i = get_size();// 一个函数赋值 运行时初始化
const int j = 42;//编译时初始化
const int k;//错误,常量定义式必须初始化
double dvel = 3.14;// 也可以由其他类型变量 强制转换成 常量
const int ci = dvel;// 由double类型变量 强制转换成 整形常量 3
extern const int bufSize = 1024;//定义了一个变量,想在其他文件也使用 bufSize 必须在定义之前 加extern
extern const int bufSize;// 另一个文件中 声明 bufSize 后 就可以使用了
绑定到常量的引用 const 的引用 即对常量的引用 reference to const 常量 的 别名 不能修改 不存在常量引用 &const 引用不是对象
const int c_i = 1024; // 常整数
const int &r_c_i = c_i; // 常整数 c_i 的 引用(别名)
r_c_i = 42;// 错误r_c_i 是常量引用 不能被修改
int &r_c_i2 = c_i;//错误 常整数 不能赋值给 int变量 左右两边类型必须一样
允许将一个常量引用绑定到 非常量对象、字面值,甚至是个 一般表达式
int i = 52;
const int &r1 = i; // 允许将 常量引用 const int & 绑定到 int变量上
const int &r2 = 42;// 绑定到 字面值上
const int &r3 = r1 * 2;// 绑定到一个 表达式上
int &r4 = r1 * 2;// 错误,r4是非常量 引用,只能绑定到 其对应类型的对象上
double dval = 3.14;//浮点数
const int &ri = dval;//常量引用 绑定到 非常量上
//相当于 先把 非常量转化成常量 强制类型转换 常量引用实际上绑定了一个临时变量
const int temp = dval;//
int i = 42;// int 变量
int &r1 = i;// 整数变量引用 r1为 i 的别名
const int &r2 = i;//常量 引用 r2 绑定到 变量 i上
r1 = 0;// 相当于 i =0
r2 = 0;// 错误 r2 时常量引用 不允许改变
指向常量的指针 const int * 指针指向的值不能变 也就是 *p 不能被赋值 不能改变
const double pi = 3.14;//双精度 浮点型 常量
double *ptr_d = π// 错误,浮点型变量指针 不能绑定一个 常量的存储地址
const double *ptr_d_c = π// 双精度常量 针 ptr_d_c 指向一个 双精度常量
*ptr_d_c = 42;// 不能给 pi 赋值 指向常量的指针的解引用相当于 绑定的常量 ,因此不能赋值
doubel dvel = 3.14;//双精度变量
ptr_d_c = &dvel;//允许 常量指针ptr_d_c 指向一个变量dvel 但是不能通过 常量指针ptr_d_c 修改变量dvel
常量指针 *const 指针本身不能改变 也就是指向不能改变 p不能改变 但是其指向的对象 无影响
int errNumber = 0;//
int *const conpErr = &errNumber;// * 可变指针 *const 常量指针不可变
// 指向整形的 常量指针,conpErr 一直指向 errNumber
*conpErr = 3;//相当于 errNumber = 3
const double pi = 3.14;//
const double *const cd_cp = π//指向 常量的常量指针,
// 即 指针本身cd_cp不能变, 其指向的值 *cd_cp也不能变
常量表达式 编译时就能确定的量/式子 constexpr int ce = 20;
const int *p = nullptr;// 指向整形常量 的指针
constexpr int *p = nullptr;// 指向整形变量的 常量指针
// constexpr 声明中出现 指针 仅仅说明 指针为常量指针
int *const p =nullptr;//指向 整形变量的 常量指针
const int *const p = nullptr;// 指向 整形常量 的常量指针
constexpr const int *p = nullptr;// 指向 整形常量 的常量指针
constexpr int ci = 42; // ci 是整形常量
int j = 0;
constexpr int *pci = &j; // 指向整形的 常量指针
处理类型
类型别名 type alias
typedef double wages; // wages 是double类型的 同义词
typedef wages base, *p; // base 也是double类型的 同义词。 p是double * 的同义词
wages d_money = 1.00;//等有价于 double d_money = 1.00
p p_dmoney = &d_money;//等价于 double *p_dmoney = &d_money;
cout << p_dmoney << endl;
// 别名声明 alias declaration
using SI = Sales_Item; // SI 是 Sales_Item 的 同义词
SI item;//等价于 Sales_Item item;
// 指针、常量const 类别别名
typedef char *pstring;// pstring等价于 char * 指向char 的指针(是指针)
const pstring cstr = 0;// cstr是指向 char 的 常量指针
// !!!!不是指向常量字符的 指针 不能直接替换 const修饰的主语是指针
const pstring *ps; // ps是一个指针 它指向的对象 是 指向char的常量指针
auto 类型说明符 让 编译器根据右式 类型 自动推算左式的类型 且用表达式的值 初始化变量
auto item = val1 +val2;//item 初始化为 val1 和 val2相加的结果 类型 相同
// 一条语句定义多个变量时,各变量类型必须一致
auto i=0, *p = &i;//正确 i是整数, p是指向整形的指针
auto sz = 0, pi = 3.4;// 错误 sz 和 pi 类型不一致
// 引用 指针 常量 与 auto
int i = 0, &r = i;//r是i的别名 int类型
auto a = r;// a 是一个整形数
// auto 会忽略掉 顶层const
const int ci = i, &cir = ci;// 常整数
auto b = ci; // b是一个整数,ci的顶层 const(最外层修饰 为顶层)特性被忽略
auto c = cir; // c是一个整数,ci的顶层 const特性被忽略
auto d = &i; // d是一个指向整形的指针
auto e = &ci; // e是一个指向整形常量的指针 const
// 外又被 取地址符号修饰,所以这里的 const是 底层const 不被忽略
// 如果希望 auto 推断出的类型是 一个顶层 const,需要在其前面 明确指出
const auto f = ci; // ci的推演类型是 int ,f是 const int
// 将引用设置为 auto 还按之前的初始化规则 保留 顶层 const
auto &g = ci; // g 是一个 整形常量的 引用 (别名)
auto &h = 42; // 错误,非常量 引用 不能绑定 到 字面值
const auto &j = 42; // 正确, 常量引用可以绑定到 字面值
// 将 指针设置为 auto, 也按之前的初始化规则 保留顶层 const
auto *k = &ci; // k为指向 整形常量的 指针
auto *l = 0; // l为空指针
const auto *m = &ci; // m 为 指向整形常量的 常量指针
int i = 1024;
const int c_i = i;
auto b = c_i; // b是一个 整数 ,c_i的 顶层 const被忽略
auto e = &c_i;// e 是一个 指向 整形常量的引用 这里的const是底层const 不被忽略
auto k = c_i, &ll = i;// k 是整数, ll 是一个整数引用
auto k = c_i, &o = c_i;// 错误,k 是整数变量 o是一个整数常量引用 类型不一致
auto &m =c_i, *p = &c_i;//正确 m是整数常量引用,p是指向整数常量的指针
auto n = &c_i, *p = &c_i;// 正确 n是整数常量 引用(底层const,不被忽略) p是指向整数常量的指针
decltype 类型指示符 用表达式推断类型 但是不用表达式的值初始化变量
decltype(f()) sum = x; // sum 的类型为 函数 f() 的返回值类型 初始化为 x 就像 int sum = x; 一样
const int ci = 0, &cj = ci;// ci 整形常量 cj 整形常量的引用
decltype(ci) x = 0; // x的类型 同ci 是 整形常量 const int 不忽略 顶层 const
decltype(cj) y = x; // y的类型 同cj 是 整形常量的引用 const int & y绑定到x上
decltype(cj) z; // 错误 z是一个 引用 ,必须初始化
// decltype和引用 指针
int i =42, j=12, *p = &i, &r = i;
decltype(r) a = j;// a的类型和r的类型一致,为整数的引用 int& a绑定到j上
decltype(r + 0) b;//正确 加法的结果为 整形 int 因此 b为整形, 这里只定义 没有初始化
decltype(*p) c; // 错误 其实*p 解引用指针 相当于i的别名 也就是引用
// 所有这里 c的类型为 整数引用 int &,是个引用,必须初始化
// decltype 的表达式如果 加上括号,得到的结果将是引用
decltype(i) e; // e 是一个未初始化的 整形变量
decltype((i)) d = e; // d是一个 整数的引用 绑定到 整形变量 e上 双层引用括号 的结果 永远都是
// 赋值表达式 也会产生 引用 引用的类型就是 等式左边变量的类型 如果 i是int i = x的类型是 int&
标准库 std
字符串类 string 类 #include 存储一个可变长度的 字符串 使用内置数组类型实现
范围for 访问所有元素
string str("some string");
for (auto c : str)// auto自动推断类型
cout << c << endl;
// 范围for 引用 改变 内容
string s("Hello World!!!");
for (auto &c : s)// c是 字符串中每个元素的一个别名
c = toupper(c);//变成大写
cout << s << endl;
下标运算符(索引)[] 和 迭代器 访问单个元素
string的下标 类型为 string::size_type 无符号数 可用 decltype(s.size())获取 s[0] 是第一个字符 s[s.size()-1]是最后一个字符
// 第一个字符改为大写
string s("some string");
if(!s.empty())
s[0] = toupper(s[0]);//第一个字符改为大写 在 cctype头文件中
// 第一个单词改为大写
for(decltype(s.size()) index = 0;
index != s.size() && !isspace(s[index]; ++index))
s[index] = toupper(s[index]);
向量 模板 容器container vector 模板 #include 存储一个可变长度的 对象 集合 使用内置数组类型实现
因为 vector 可以存放任意类型 所以事先需要知道 存放的对象是什么类型 vector ivec; vector; vector<vector >;
// 初始化方式
vector<int> ivec(10,-1);// 直接初始化 10个元素 全为 -1
vector<int> ivec2 = ivec;//拷贝初始化
vector<int> ivec3{10};//一个元素 10
vector<int> ivec3{10,1};//两个元素 10 和 1
vector<string> svec{"a","an","the"};//列表初始化 直接方式
vector<string> svec2 = {"a","an","the"};//列表初始化 拷贝方式
// 默认初始化
vector<int> ivec(10); // 10个元素,每个值都是0
vector<string> svec(10); // 10个元素,每个值都是空 string 对象
vector<string> svec2{10};// 10个元素,每个值都是空 string 对象
vector<string> svec3{10, "hi"};// 10个 "hi"元素
vector<string> svec3(10, "hi");// 10个 "hi"元素
// 使用 .push_back() 添加元素
vector<int> ivec2; //空vec对象
for(int i = 0; i != 100; ++i)
ivec2.push_back(i);// 一次把整数值 放到 ivec2尾部 结束后 ivec2有100个元素 0~99
// 实时添加 string 元素
string word;
vector<string> text;//空对象
while (cin >> word)
text.push_back(word);// 把word添加到 text 后面
//使用范围for + 引用 访问 并改变vector元素
vector<int> iv{1,2,3,4,5,6,7,8,9};// 列表直接初始化
for (auto &i : v)// 对于v中每个 元素的 引用 需要改变其值
i *= i; // 变成原来值 的 平方
for (auto i : v) // 仅读取其中的变量
cout << i << " ";
cout << endl;
// vector 对象大小 类型为size_type
vector<int>::size_type se = iv.size();
// 使用索引[] 访问 计算vector对象元素索引 统计各个分数段上 出现的 成绩个数
// 索引不能添加元素
vector<unsigned> scores(11,0);//11个分数段, 0~9,10~19,...,90~99,100 计数值全部初始化为0
unsigned grade;
while (cin >> grade){
if(grade <= 100) ++scores[grade/10];
}
// cin 读入一组词 改成大写 存入 vector中 #include <cctype> 使用toupper()
vector<string> sv;
string word1 = "qwe";
cout << word1 << endl;
while(cin >> word1){
for (auto &c : word1) c = toupper(c);
sv.push_back(word1);
cout << word1 << endl; vector
}
迭代器 访问容器中的 元素 auto b = v.begin(), e = v.end(); b表示v的第一个元素 e表示v尾元素 的下一个位置 类似 指针
// 修改 字符串 第一个元素为大小字符
string s("some string");
if (s.begin() != s.end()){//确保 s非空
auto it = s.begin();// it 指向 s的第一个字符 类似指针 的作用
*it = toupper(*it);// 将当前字符改写成大写形式 *it 解引用迭代器 得到其所指向的 对象 是其指向对象的别名 引用
}
// 字符串的第一个单词 改写成大写
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)
*it = toupper(*it);
// 迭代器类型 iterator (具有读写功能) const_iterator 具有 读功能 不具有写功能
// 对象为常量 只具有常量类型的迭代器 const_iterator 对象为变量具有 iterator 和 const_iterator
vector<int>::iterator it;// 迭代器 it 可以读写 vector<int> 类型容器 的元素
string::iterator it2; // it2 可以读写 string对象 中的字符
vector<int>::const_iterator it3;//it3只能读元素,不能写元素
string::const_iterator it4; //it4只能读字符,不能写字符
// cbegin() cend() 返回 常量 迭代器 仅能读取 容器元素 不能修改
vector<int> iv; // 变量
const vector<int> civ; // 常量
auto it1 = iv.begin(); // it1的类型为 vector<int>::iterator 能读写iv的元素
auto it2 = iv.cbegin(); // it2的类型为 vector<int>::const_iterator 能读iv的元素 不能修改 iv的元素
auto it3 = civ.begin(); // it3的类型为 vector<int>::const_iterator 能读civ的元素 不能修改 civ的元素
// 访问 容器元素对象的 成员函数 (*it).empty 等同于 it->empty() 解引用 和成员访问
vector<string> text;
for (auto it = text.cbegin(); it !=text.cend() && !it->empty(); ++it)
cout << *it << endl;
// 迭代器 运算
auto mid = iv.begin() + iv.size()/2; //指向容器的中间位置
if (it < mid) // 处理前半部分元素
// 两个迭代器相减 得到的类型为 带符号整数 difference_type
// 常规二分查找算法
// 升序数组 查找的元素 范围开始 结束
int BinarySearch(int *array, int key, int low, int high)
{
int mid;
while (low <= high)// 缩小范围
{
mid = (low + high) / 2;//更新中间元素的 下标
if (key == array[mid])//中间元素是否 等于所查找的元素
return mid+1;//相等 返回元素下标
else if (key < array[mid])//所查元素 比 中间元素小 则在 前区间查找
high = mid - 1;//将区间 右侧 退后 到 中间元素下标前一个元素 搜索 范围为 low,mid-1
else//所查元素 比 中间元素大 则 在后区间查找
low = mid + 1;//将区间 左测 提至 中间元素下标后一个元素 搜索 范围 mid+1,high
}
return 0;
}
// 使用迭代器完成二分查找
vector<int> text// 升序容器
auto b = text.begin(), e = text.end();//起始 结束位置
auto mid = b + (e - b)/2;//中间位置
while(low <= end && *mid != key){
if(key < *mid) e = mid - 1;//所查元素 比 中间元素小 则在 前区间查找
else b = mid + 1;// 所查元素 比 中间元素大 则 在后区间查找
mid = b + (e - b)/2;//更新 中间位置
}
// 使用索引[] 访问 计算vector对象元素索引 统计各个分数段上 出现的 成绩个数
// 索引不能添加元素
vector<unsigned> scores(11,0);//11个分数段, 0~9,10~19,...,90~99,100 计数值全部初始化为0
unsigned grade;
while (cin >> grade){
if(grade <= 100) ++scores[grade/10];
}
// 使用迭代器 访问 计算vector对象元素索引 统计各个分数段上 出现的 成绩个数
vector<unsigned> scores(11,0);//11个分数段, 0~9,10~19,...,90~99,100 计数值全部初始化为0
unsigned grade;
auto it0 = scores.begin();
while (cin >> grade){
auto it = it0 + grade/10;
if(grade <= 100) ++*it;
}
数组
数组与标准库 类型 vector 类似,都存放类型相同对象的容器。 需要通过其所在的位置访问对象。 与vector不同的是,数组大小确定不变,不能随意向数组中增加元素, 数组不允许拷贝,vector允许拷贝。 注意数组名 相当于数组首元素的地址 ia[10] ia === &ia[0]
【1】定义
constexpr unsigned sz = 42;//constexpr修饰,常量表达式
int arr[10]; //字面值常量初始化 含有10个整数的数组
int arr2[sz]; //常量表达式初始化 含有42个个整数的数组
int *parr[sz];//常量表达式初始化 含有42个指向整数的指针的数组
定义时必须指定数组类型,不能由auto来推断
不存在引用的数组,引用不是对象!!!
string nums[] = {"one", "two", "three"};// 数组元素是string对象
string *sp = &nums[0];// p指向nums的第一个元素
string *sp2 = nums; // 等价于 string *sp2 = &nums[0]
auto sp3(nums); //sp3 是一个string指针,指向nums的第一个元素
// 而decltype关键字 声明的 不发生上述转换
decltype(nums) sa = {"two", "three", "four"};//sa 是一个 含有3个string对象的 数组
##【2】显式初始化数组元素
const unsigned sz = 3;//
int ia1[sz] = {0, 1, 2};//列表初始化 含有3个元素
int ia2[] = {0, 1, 2};//维度为3
int ia3[5] = {0, 1, 2};//等价于 {0, 1, 2, 0, 0}
// 字符数组
char ca1[] = {'C', 'P', 'P'};//列表初始化
char ca2[] = {'C', 'P', 'P', '\0'};//含有显式的 空字符
char ca3[] = "CPP";//字符串字面值初始化 自动添加表示字符串结束的空字符
// string 对象初始化 字符数组
string s("Hello World");
const char *str = s.c_str();// 用string对象初始化 字符数组 需要使用 c_str() 方法 最好再重新拷贝一份
// 数组 初始化 vector 对象
int i_arr[] = {1, 2, 3, 4, 5, 6};
vector<int> ivec(begin(i_arr), end(i_arr));//全部副本
vector<int> sub_ivec(i_arr + 1, i_arr + 4);// 包含 {2, 3, 4, 5}四个元素
// 不允许拷贝和赋值
char ca4 = ca3;// 错误
// 复杂的数组声明定义
int *parr[10];// 是数组,包含10个整形指针的数组
int &rarr[10]=?;//错误,不存在 引用数组,引用不是对象
int (*Parray)[10] = &arr;//是指针,指向一个含有10个整数的数组
int (&Rarray)[10] = arr;//是引用,引用一个含有10个整数的数组
【3】访问数组元素
与标准库类型vector 和 string 一样,数组元素也可以使用 范围for语句
或下标运算符 访问,元素下标从0开始,下标通常定义为 size_t类型,unsigned类型。
标准库类型vector 和 string 下标运算符索引必须为正值 unsigned类型,数组下标运算符索引 为signed类型,内置类型,可以为负值
//下标访问修改元素
unsigned score[11];//11个分数段
unsigned grade;
while(cin >> grade){
if(grade <= 100) ++score[grade/10];//对应段 计数+1
}
// 范围for 访问修改所有元素
for( auto i:score)//可以设置为 引用就可以修改元素了
cout << i << " ";
cout << endl;
// 指针访问数组
int iarr[] = {0,1,2,3,4};//含有5个元素
int *pi = iarr;//指向第一个元素的指针 iarr[0]
int *pi2 = iarr + 2;//指向第三个元素的指针 iarr[2]
auto num = end(iarr) - begin(iarr); // num的值是5 就是iarr包含元素的数量
// ptrdiff_t 类型 是signde类型 结果可能为负
++pi;//指向第二个元素 iarr[1]
j = pi[1]; // 等价于 *(p+1),就是 iarr[2], 就是 2
k = pi[-1]; // 等价于 *(p-1),就是 iarr[0], 就是 0
pi + 3;//指向最后一个元素
*pi;//第二个元素 4
int last = *(iarr + 4);// 等价于 last = iarr[4];
int *end = &iarr[6];//指向尾后的位置 到达不了 不能执行解引用运算!!!!!
// 使用 for
fot(int *begin = arr; begin != end; ++begin)
cout << *begin << endl;//输出每一个元素
// 使用 while
while(begin<end){//指针指向相关的对象 可以比较大小(单位(间隔)一样大)
cout << *begin << endl;//输出每一个元素
++begin;
}
//标准库函数 begin() end() 函数得到指针
int iarr = {0,1,2,3,4};//函数5个元素
int *beg = begin(iarr);//首元素指针
int *end = end(iarr);//尾后指针 不能执行解引用运算!!!!!
while(beg != end && *beg >=0) ++beg;//找第一个负值元素
多维数组 数组的数组
初始化
constexpr size_t row =3, col = 4;
int iarr[row][col];//定义未初始化
//下标运算符(size_t 类型) for 循环初始化
for(size_t i = 0; i != row; ++i){
for(size_t j = 0; j != col; ++j){
iarr[i][j] = i * col + j;//元素索引为其 值
cout << iarr[i][j] << ' '; // 访问输出 数组元素
}
cout << endl;
}
// 范围for 初始化
size_t cnt = 0;
for (auto &row1 : iarr)//每一行 引用 int (&row1)[4] 是引用 引用一个含有4个整数的数组
{
for (auto &col1 : row1){// int &col1 每一行的每一个元素 引用 可以读写 除去最内层外 其它必须使用 引用
col1 = cnt;//赋值
cout << col1 << ' ';// 访问 输出数组元素
++cnt;
}
cout << endl;
}
访问
// 指针访问
int (*p)[4] = iarr;//p 指向一个含有4个整数的数组 iarr的 第一行 圆括号必不可少
p = &iarr[2]; // iarr的 第3行
// for 循环访问
for( auto p = ia; p != ia + row; ++p){// 相当于定义 int (*p)[4] = iarr; p 指向 含有4个整数的数组
for( auto q = *p; q != *p + col; ++q)
// *p 为含有4个元素的数组 q指向数组的首元素,即指向一个整数 int *q = *p
cout << *q << ' ';
cout << endl;
}
// 使用 标准库函数 begin() 和 end()
for( auto p = begin(ia); p != end(ia); ++p){
// 相当于定义 int (*p)[4] = iarr; p 指向 含有4个整数的数组
for( auto q = begin(*p); q != end(*p); ++q)
// *p 为含有4个元素的数组 q指向数组的首元素,即指向一个整数 int *q = *p
cout << *q << ' ';
cout << endl;
}
// 使用类型别名
using int_arr_tpye = int[4];// int_arr_tpye为包含4个元素的整形数组
typedef int int_arr_tpye[4];// 效果同上
for( int_arr_tpye *p = ia; p != ia + row; ++p){// 相当于定义 int (*p)[4] = iarr; p 指向 含有4个整数的数组
for( int *q = *p; q != *p + col; ++q) // *p 为含有4个元素的数组 q指向数组的首元素,即指向一个整数
cout << *q << ' ';
cout << endl;
}
函数
包括:返回类型 函数名字(形参列表) 函数体,函数的返回类型不能是数组或函数类型,但可以是 数组或函数类型的指针
函数定义 阶乘函数
int fact(int val){
int ret = 1;
while(val >1) ret *= val--;
return ret;// 返回主调函数 结束函数调用
}
函数调用 分两步 1 实参初始化形参(形参为引用时,直接使用实参) 2控制权转移给被调用函数(inline 内联函数 之间在原处展开函数)
int main(){
int j = fact(5);// 实参5 初始化 形参(int val)
cout << "5! = " << j << endl;
return 0;
}
局部对象
形参和函数体内定义的变量统称为 局部变量,仅在函数的作用域内可见, 局部自动对象,只存在于函数体执行期间,而局部静态对象,可在函数调用后一直存在
// 定义
int count_call(void){
int c =0; //局部自动对象 每一次调用都初始化为 0
static int sc =0;//局部静态变量 第一次调用初始化为0 以后每次调用在前一次值上 +1
cout << ++sc << " " << ++c << endl;
}
// 调用
int main(){
for(int i =0; i<10; ++i) count_call(); //sc 输出 1 2,...10,c 输出一直是1
return 0;
}
分离式编译 函数声明 与函数定义类似但是 不包括函数体,以;代替函数体 int fact(int val); 函数可以多次声明
// 源文件 fact.cc
#include "fact.h"
int fact(int val){
int ret = 1;
while(val >1) ret *= val--;
return ret;// 返回主调函数 结束函数调用
}
// 函数声明头文件 fact.h
#ifndef FACT_H
#define FACT_H
int fact(int val);// 函数声明
#endif
// 主函数调用 fact_main.c
#include <iostream>
#include "fact.h"
using namespace std;
int main(){
int j = fact(5);// 实参5 初始化 形参(int val)
cout << "5! = " << j << endl;
return 0;
}
// 编译
gcc fact_main.c fact.cc -o main
参数传递
当形参是引用类型时,为引用传递,实际传递的是实参的别名,没有进行拷贝,当实参的值被拷贝给形参时,形参和实参是两个独立的对象
值传递 函数对形参做的所有操作 都不会影响实参
int n=0;
int i = n;// n拷贝给i
i = 42;//i的值改变, n的值不变 函数对形参做的所有操作 都不会影响实参 例如 fact(i) 不会改变i的值
引用传递 函数对形参做的所有操作 都会影响实参
int n = 0;
int &r_i = n;//r_i 是 n 的引用 即别名 同一个变量
r_i = 42;//r_i 和 n 都变成 42
指针形参
int n=0, i =42;
int *p_n = &n, *p_i = &i;// 指针
*p_n = 100;// n 的值 变为100 指针p_n(变量你存储的地址) 不变
p_n = p_i;// 现在 p_n 和 p_i 都指向了 i
// 指针形参 函数
void reset(int *pi){
*ip = 0;//改变了指针 pi 所指向的对象的值
ip = 0;// 值改变了 形参ip的值 实参未被改变
}
// 调用
int i = 42;
cout << "address of i =" << &i <<endl;
reset(&i);// i 的值改变为0
cout << "i = " << i << endl;// i 的值改变为0
cout << "address of i =" << &i <<endl;// i 的存储地址未改变
// C 程序中 通常使用 指针类型的形参 来访问和改变 函数外部的对象
// C++ 程序中,建议使用引用类型的形参代替 指针形参,这样会更安全,也省时间(引用 无拷贝操作)
// 使用 引用避免拷贝 拷贝大的类对象或容器 都比较低效 费时
引用形参
void reset(int &i){
i = 0;// 改变了i所引用的对象
}
// 调用
int j = 42;
reset(j);// 采样传 引用方式,它的值被改变 调用时 形参i 只是 实参j的一个别名,在函数reset内部对i的操作,即对j的使用
cout << "j = " << j << endl;
常量引用形参 当函数无须 改变引用参数的值时,最好将其声明为 常量引用
// 比较两个字符串的长度
bool isShorter(const string &rs1, const string &rs2){
return rs1.size() < rs2.size();
}
使用引用形参 返回额外信息
// 函数返回多个值 返回字符在某个字符串中第一次出现的位置,并返回出现的 总次数
string::size_type find_char(const string &crs, char c, string::size_type &occurs){
// 字符串 查找的字符 出现的次数
auto ret = crs.size();//初始化 第一次出现的位置
occurs = 0;//初始化 出现的次数
for(decltype(ret) i = 0; i != crs.size(); ++i){
if(crs[i] == c){// 出现字符 c
if (ret == crs.size())//位置 还未改变 为 第一次出现
ret = i;//记录第一次出现的位置
++occurs;//出现次数 +1 通过形参引用间接返回 出现的次数
}
}
return ret;//返回第一次出现的位置
}
// 调用
string s("some string");
string::size_type count = 0;
auto index = find_char(s, 'o', count);
// 判断 string对象是否是 一个句子
bool is_sentence(const string &crs){
// 如果 find_char() 的string参数 必须为 string & 那么不能直接把 const string & 带入
// 需要再定义一个 string对象, 另其为 crs 的副本,再带入
string::size_type count = 0;
return (find_char(crs, '.', count) == (crs.size() - 1)) && count == 1;
}
数组形参 必须确保使用数组时不会越界 通常需要给一个结束标志
1 不允许拷贝数组
2 在使用数组时会将其转换成指针(指向数组首元素的指针)
// 三个函数等价 形参 都是 const int * 类型
void print(const int*);
void print(const int[]);
void print(const int[10]);// 这里的维度表示期望数组含有多少元素,实际不一定
//调用
int i = 0;
j[2] = {0, 1};
print(&i); //&i 是int *类型 可以赋值给 const int *类型
print(j); //j 为 &j[0] 是 int * 类型
使用标记指定数组长度 要求数组本身包含 一个结束标志 典型 C风格字符串 带有一个空字符
// C风格字符串 带有一个空字符
void print(const char *cp){
if(cp)// 确定 cp 不是一个空指针
while(*cp) // 只要 指针所指的字符不是 空字符
cout << *cp++;// 打印 字符
}
使用标准库规范 传递 数组元素的 首末地址
void print(const int *beg, const int *end){
while(beg != end)
cout << *beg++ << endl;//输出当前元素 并将指针向前移动一个位置
}
// 调用
int j[2] = {0, 1};
print(begin(j), end(j));// 使用标准库 begin() 和 end() 函数
显示传递 一个数组大小的形参
void print(const int ia[], size_t size){
for (size_t i = 0; i != size; ++i)
cout << ia[i] << endl;// 等价于 cout << *(ia+i) << endl;
}
// 调用
int j[2] = {0, 1};
print( j, end(j) - begin(j) );// 使用标准库 begin() 和 end() 函数 做差来得到数组的大小
数组引用形参 int (&r_arr)[10] 是一个含有10个元素数组的 引用 直接包含了 数组大小的信息
void print(int (&r_arr)[10]){ // 注意形式 int (&r_arr)[10] 而 int &arr[10] 成了 包含引用的数组(还不存在,引用不是对象)
for (auto elem : r_arr)// 变量数组每个元素 范围for
cout << elem << endl;
}
// 调用 调用时 必须是 大小为10的整形 数组作为实参才可以
int k[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
print(k);// 大小必须为10 多一点 少一点 都不行
多维数组形参 数组指针 形参
void print(int (*p_arr)[10], int rowSize){}// 形参是 指向 一个含有10个整数的指针
main函数 主函数参数列表
// argc:argument counter 参数计数,argv:argument vector 参数 字符串 容器
int main(int argc, char *argv[]){}
int main(int argc, char **argv){}//第二个参数为 字符串数组 即数组的数组 指针的指针 argv 指向 char*
//程序执行 program -d -o outfile data0
则 argc = 5
argv[0] = "program"; // 程序名
argv[1] = "-d"; // 第一个参数 开始
argv[2] = "-o"; // 第二个参数
argv[3] = "outfile"; // 第三个参数
argv[4] = "data0"; // 第四个参数 最后一个指针
argv[5] = 0; // 最后一个指针之后 的元素为0
含有可变形参的函数 1 传递一个 initializer_list 标准库类型,2 特殊参数类型 省略号 ...
initializer_list 形参 实参数量未知 但是全部实参类型 都 相同
// initializer_list 提供的操作
initializer_list<T> lst;// 默认初始化 T类型元素的空列表 模实际需要指定模板T的 具体类型
initializer_list<T> lst{a, b, c...};// 列表中的元素是 const 常量
lst2(lst); // 拷贝一个initializer_list对象,不会新建,原始列表 和 副本 共享元素
lst2 = lst;// 同上
lst.size();// 列表 中的元素
lst.begin();//返回指向lst中首元素的指针
lst.end(); //返回指向lst中尾元素下一个位置的指针
// 因为有 begin() 和 end()对象可以使用 范围for 遍历参数
// 具体
initializer_list<string> ls ;//initializer_list 的元素类型是 string
initializer_list<int> li ; //initializer_list 的元素类型是 int
// 和vector不同的是 initializer_list中的元素是常量 不能被修改
// 定义函数
void error_msg(initializer_list<string> ls){
for(auto beg = ls.begin(); beg != ls.end(); ++beg)
cout << *beg << " ";
cout << endl;
}
//调用函数
//expected 和 actual 是string 对象
if (expected != actual)
error_msg({"function", expected, actual});
else
error_msg({"function", "okey"});
// 传递了一个含有不同数量元素的 initializer_list
// 定义函数 包含 ErrCode
void error_msg(ErrCode e, initializer_list<string> ls){
cout << e.msg() << ": "
for(const auto &elem : ls )//范围for 遍历
cout << elem << " ";
cout << endl;
}
省略符 ... 形参
void foo(param_list, ...)//
void foo(param_list...)//
void foo(...)//
函数 返回类型 和 return语句
无返回值函数 void函数 无需显示的 return 语句 return 后无值
// 交换两个值的函数
void swap(int &r_i1, int &r_i2){
if (r_i1 == r_i2)// 两个值相等 无需交换
return;
// 若 执行 到这里,说明还需要继续完成下面的功能
int temp = r_i1;//
r_i1 = r_i2;
r_i2 = temp;
// 此处 无需 显示的 return 语句 会隐式指向 return
}
有返回值函数 return 后有值 且返回的对象类型 与 函数定义的 返回类型相同
// 两个 string 对象是否 最短的部分 是相同的
bool str_subrange(const string &str1, const string &str2){
if (str1.size() == str.size())
return str1 == str2; // 之间判断 相同长度间的部分是否相同
auto size = (str1.size() < str2.size()) ? str1.size() : str2.size(); //得到最短 字符串的 长度
for(decltype(size) i = 0; i != size; ++i){
if ( str1[i] != str2[i])// 如果 有 不相等的 字符 返回 false
return false;// 有 不相等的 字符 返回 false
}
return true; // 否者 相等 返回 true
}
返回类型为 引用时 不会对结果进行拷贝 节省时间 但是 不要 返回 函数内部 临时变量的 引用, 临时变量离开函数后就不存在了
建议 返回 引用对象 是一个 调用函数 之前就纯在的一个变量
同时 返回 非常量的引用 可以作为 左值 被赋值
// 不要 返回 函数内部 临时变量的 引用
string temp("glo");
const string &mainip() {
string ret("Empty"); // 函数内部临时变量
return ret; // 错误 ,不能返回 临时变量 作为 函数返回值的 引用
// return temp;// 返回 一个调用函数之前就出现的 变量 的引用 或者 参数 为引用类型的参数也可以
}
// 返回 两个字符串中 短的那个
const string &shortString(const string &s1, const string &s2){// 返回 s1 或者 s2的引用
return s1.size() < s2.size() ? s1 : s2;//返回 两个字符串中 短的那个
}
// 函数返回类型 为 标准库 类类型 可以直接调用 其成员函数
auto sz = shortString(s1, s2).size()// 得到最短字符串 的长度
// 返回 非常量的引用 可以作为 左值 被赋值
char &get_char(string &str, string::size_type id){
return str[id];// 获取指定位置的 字符
}
// 调用
string s("a value");
cout << s << endl;// a value
get_char(s, 0) = 'A';// 将s第一个位置上的字符 替换为 大写的 A
cout << s << endl;// A value
// 而返回为 常量引用 的不能被赋值
shortString("hi", "bye") = "X"; // 错误 函数返回值是个 常量引用 不能被赋值
函数返回类型为 列表 以花括号 {} 包围
vector<string> process(){
if(expected.empty())
return {};// 返回一个 空vector对象
else if (expected == actual)
return {"function", "okey"};
else
return {"function", expected, actual};
}
函数递归 返回值为自身函数 的一个 式子 main 函数不能调用自己
// 计算阶乘的函数
int factorial( int val){
if(val > 1)
return val * factorial(val-1);
else
return 1;
}
// 递归 返回一个vector的元素函数
void print_vec(vector<int> vi){
auto it = vi.begin();
if(vi.size() > 1)
{
cout << *it <<endl;
vi.erase(it);
print_vec(vi);
}
else
cout << *it <<endl;
}
返回 数组 指针 数组不能被 拷贝 所以函数不能返回数组 但可以返回 数组的指针 或引用
// 使用类别别名
typedef int arrT[10];// arrT是一个类别别名 表示一个 含有 10个整数 的 数组
using arrT = int[10];// 同上
arrT* func(int i); // 函数func 返回一个 指向 10个整数的数组的 指针
//直接声明
int arr[10]; //arr 是一个 含有 10个整数的数组
int *arr_p[10]; //arr_p 是一个数组 含有 10个指整形针 的 数组
int (*p2)[10] = &arr; // p2 是一个指针, 指向一个含有 10个整数的 数组 arr
// 函数声明 类似
Type (*function_name(parameter_list))[dimension]
int ( *func(int i) )[10];// 声明一个函数 其形参为 int i ,返回类型 为 指针 ,指向 一个 含有 10个整数的 数组
// 函数指针数组
多个函数可以像数组一样被调用
int (*fun_p_a[])(char *tmp); //函数指针数组
// 使用 auto 和 尾置返回类型
auto func(int i) -> int(*)[10];// 声明一个函数 其形参为 int i ,返回类型 为 指针 ,指向 一个 含有 10个整数的 数组
// 使用 decltype 知道返回数组 类似类别别名 声明 定义
int odd[] = {1, 3, 5, 7, 9};
int even[] = {0, 2, 4, 6, 8};
decltype(odd) *arrPtr(int i){ //使用decltype(odd) 表面类型与 odd类似
return (i%2) ? &odd : &even;// 返回一个指向数组的指针
}
// 声明一个 返回 引用 一个 含有10个string对象的数组 的函数
string ( &func_r(string str) )[10];// 直接声明
// 使用类别别名
typedef string arrS[10];// 类别别 含有10个string对象的数组
using arrS = string[10];// 同上
arrS &func(string str); // 函数func 返回一个 指向 10个整数的数组的 指针
// 使用 auto 和 尾置返回类型
auto func(string str) -> string(&)[10];
// 使用 decltype 知道返回数组 类似类别别名 声明 定义
string str_arr[] = {"a string", "two"};
decltype(str_arr) &arrPtr(string str);
函数 重载 同一个作用域内 的几个函数名字相同,但形参列表不同,成为 重载 (overloaded)
// 打印 数组元素的 几个 同名函数
void print(const char *cp);//出入参数为 带有结束符的