Skip to content

Latest commit

 

History

History
1348 lines (1156 loc) · 58.5 KB

README.md

File metadata and controls

1348 lines (1156 loc) · 58.5 KB

参考资料

gdb 调试入门,大牛写的高质量指南

Computer Architecture A Quantitative Approach pdf 翻译

自动编程体系设想

程序猿成长计划

数据结构和算法动态可视化

基础数据结构和算法的纯C语言实现

计算机科学 面试笔记

计算机科学 面试笔记2

可视化代码过程

各种工程实践代码参考

c++ 多线程 并发 指南 实战

c/c++教程

学习C++,应该循序渐进的看哪些书?

数据结构与算法系列 目录

刷完这65道题,面试通过率翻翻没问题

c++代码实验

源代码查找 热度 书籍

源代码查找 英文

免费的编程中文书籍索引

代码面试较好 强烈推荐!!!!!

nowcoder刷题笔记

LeetCode刷题笔记

跟我一起复习C++

Leetcode常用五大算法思想

数据结构 博客参考 树 图 队列

GitHub Pages+Jekyll搭建个人博客

Markdown工具集

清华操作系统 github

博客专栏 Linux环境编程

博客专栏 Linux网络编程

博客专栏 一步步学习C++

博客专栏 C语言

鱼C工作室 C/C++/Python/Wed/数据结构和算法

图形界面编程

面试算法博客笔记

刘国平基础算法

CPP11新特新 代码实战

新特征 C++11/14/17 concepts and code snippets

A Detailed Cplusplus Concurrency Tutorial 《C++ 并发编程指南》

基于C++11新标准的并发和多线程编程深度指南 C++ Concurrency In Action

《C++17 STL cookbook》英文版的中文翻译

哈希学习

甲骨文公司编辑器Oracle Solaris Studio 12.4 Information Library (简体中文) c/cpp用户指南 数值计算指南 代码分析器 性能分析器 线程分析器

哈工大 编译原理

编译原理——词法分析器实现

编译技术 西电

在线书籍编写模板 gitbook

循序渐进学习书目

svn新建trunk 和 分支

感谢支持

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

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 = &pi; //指向整形指针的指针  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 = &pi;// 错误,浮点型变量指针 不能绑定一个 常量的存储地址
const double *ptr_d_c = &pi;// 双精度常量 针 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 = &pi;//指向 常量的常量指针,
                               // 即 指针本身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{101};//两个元素 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);//出入参数为 带有结束符的