前言:工作后吃饭的语言是 java,同时写一些 python 和 js,在学习机器学习的时候发现有必要再熟悉一下 c++,同时工作也有 c++ 的使用需求.于是开始对照 c++ primer 自学,希望能够对同样是其他语言的学习者,在学习 c++ 的时候提供一些帮助.
第 1 章: 起始
First program
主流编译器:GNU 编译器和微软的编译器,运行微软编译器的命令是:cl
Input/Output
Using namespace used to avoid inadvertent collsions between the same names.Std::cin 返回 std::cin 对象
注释
C++ 中有两种类型的注释:单行注释:// 和多行注释:/* */ 如果有程序如下:
上面这段程序会一直读取输入的内容直到输入的结尾,在不同的操作系统中,标识输入的结尾是不同的,在类 unix 操作系统中,使用 ctrl + D 来表示输入结尾,在 windows 系统中,使用 ctrl+Z 标识.
#include <iostream>
int main(){
int sum = 0, value = 0;
while(std::cin >> value){
sum += value;
}
std::cout<< "Sum is: "<< sum << std::endl;// std::endl flush buffer
}
关于编译器
在类 unix 系统(含 Mac)使用到的编译器和编译指令如下:关于这些区别的一篇博文: https://www.cnblogs.com/qoakzmxncb/archive/2013/04/18/3029105.html
C++ 中的类
使用一个库的时候,需要包含关联的 header 文件.标准库的 headers 一般是不包含后缀名的,编译器一般不关心文件的后缀,但是 IDE 有时会.
clog 的使用,默认地,写到 clog 中的数据会被 buffer 缓冲,一般用于报告程序运行过程中的信息.
第 2 章:基础
C++ 的原始内建类型
C++ 中包含了一些基本的数学类型和 void 类型,作为原始内建类型.
int integer 16bits(所有操作系统?)
TypeMeaningMinimum Size
boolboolean NA
char character 8bits
wchar_t wide character 16bits
char16_tUnicode character 16bits
char32_tUnicode character 32bits
shortshort integer 16bits
long double Extended-precision 10 significant digits需要注意的是,在上述表格中的数据类型所占用的内存大小根据平台的不同而不同.确定内存地址的数据需要数据类型和读取内存地址的二进制数据,不同的数据类型决定了占用多少比特以及如何解析这些内存数据.上述的 int,long,long long 类型都是有符号数,对应的无符号数前面加上 unsigned.
longlong integer 32bits
long longlong integer 64bits
floatSingle-precision floating-point 6 significant digits
doubleDouble-precision 10 significant digits
数据类型的后缀中是 U 时,字面量是 unsigned 类型,类型可以使 unsigned int,unsigned long 或 unsigned long long 类型;
如果后缀是 L,字面量类型时 long;
如果后缀是 LL,字面量类型时 long long 或者 unsigned long long
如果后缀是 UL 或 ULL,字面量类型时 unsigned long 或 unsigned long long
前缀类型表:
初始化和定义
MeaningPrefixType
u Unicode 16 character Char16_t
U Unicode 32 character char32_t
L wide character wchar_t
u8 utf-8char
C++ 提供了多种初始化的方式:
编译器不允许使用列表初始化时类型信息丢失:
int unit = 0;
int unit = {0};
int unit{0};//列表初始化
int unit(0);
声明:声明一个名称让程序知道定义:定义除了声明名称和类型,同时申请内存和提供默认值为了得到一个变量的声明而不是定义,我们使用 extern 关键字,而且不显示的初始化变量.
long double ld = 3.1415926
Declaration & Definition
一个变量可以被声明多次,但是只能被定义一次.
extern int i; // 声明变量
int j; // 定义变量
作用域
全局作用域:定义在函数体之外的变量块作用域:{} 内的作用域
复合类型
C++ 中有多种复合类型,这里记录指针和引用.引用为对象起了另外一个名字,通过 & d 来定义引用类型,其中 d 是变量名
引用非对象,相反的,它是为对象取了一个别名为引用赋值,实际是赋值给引用的对象;获取引用的值,获取的是对象的值;将引用的值作为初始值,实际上是将引用对象的值作为初始值.因为引用本身不是对象,所以不能定义引用的引用.无法将引用绑定到另外一个对象,因此引用必须初始化.可以给引用赋值(等于给别名赋值),如下代码所示:
int a = 1;
int &d = a; // 声明引用,d是a的另外一个名字
int &d2; // 报错,引用必须初始化
指针指针时一个指向其他类型的复合类型,值是指向对象的地址.指针本身是一个对象.指针值
int i = 0;
int &ri = i;
ri = 10; // legal,这里等于是给i进行赋值
指针的值可以指向一个对象
可以指向刚刚读取完的对象的值(类似于 Iterator 执行的对象)
可以是个空指针,即没有绑定任务对象
invalid 指针,除了上述三种指针的值都是不合法的.
指针的指针可以使用 **p 获取指针的指针所在对象的值.void*void * 是一个特别的指针类型,可以保存任意对象的地址
在理解类似于 *&p 这种类型的时候,将修饰符从右往左读去理解.
const 修饰符 const 修饰的类型有普通类型的大部分操作,如将 const int 类型转为 bool 类型.
上述代码中关于 const 的操作:给 ci 赋值的时候不会考虑 ci 是常量类型,因为不会改变常量的值,同样的,将 ci 赋值给 j 的时候也是如此.默认情况下,const 对象仅在文件中有效.如果需要在不同文件间共享 const 变量的值,则使用 extern 用于声明常量并非本文件独有.常量引用常量的引用类型需要使用常量引用,如下所示:
int i = 0;
const int ci = i;
int j = ci;
const pointer 指针本身是 const,例如:
const int ci = 1;
const int &ri = ci;
int
&
r2 = ri; // error:non const reference to a const object
= π // pip is a const pointer to a const object
int num = 0;
int *const curNum = # // curNum will always point to num
const double pi = 3.14;
const
double
*
const
pip
Top-level const 使用 top-level const 来标识指针本身是一个常量;如果指针指向一个 const 对象,那么我们说这个 const 是 low-level const.constexpr 常量表达式:当做 const 表达式时可以使用 constexpr
类型处理
typedefalias 格式为:using a = A;auto 自动判断类型 decltype 自动判断类型,但是不计算变量的值 decltype() 中的解引用操作返回的结果是引用,而不是原始类型在 decltype 中添加一对以上的括号,将返回引用类型
struct 中定义的成员变量会在对象定义的时候被默认初始化.
struct
//定义struct的两种格式
struct{
};
struct {...}
a,b,*c;
定义头文件
一般只定义一次的内容放在头文件中,如类,const,constexpr 变量等.
头文件一旦改变,相关的源文件需要重新编译来获取新的变量声明.
确保头文件多次包含仍能被正确处理的机制是预处理器(preprocessor)预处理器看到 #include 会将内容替换掉#include 头文件保护符:使用以下代码来避免重复包含的发生:
#ifndef CPP_TEST_SALES_DATA_H
#define CPP_TEST_SALES_DATA_H
struct sales_data{
与编译器无视关于作用域的规则
};#endif //CPP_TEST_SALES_DATA_H
一般的做法是基于类的名字来构建保护符的名字
字:在指定机器上进行整数运算的自然单位
字符串,vector
using
格式为:using namespace::name
头文件不应该包含 using 描述,否则可能有名字冲突.
宏
C++ 中将一个标识符定义为一个字符串,源程序中的标识符用字符串代替.如:
初始化 string 的方式
#define ADD (x,y) x+y
result
=
ADD(2, 3); // 使用 x+y替换
string
其他初始化方式省略
string s(4, 'c') // 初始化为"cccc",直接初始化
string s2("hello") // 直接初始化
string s3 = "hello" //拷贝初始化
string 的操作
getline(is, s) //从is中返回一行赋值给s,返回is
其他操作省略
string::size_type 类型
不能直接使用字面量相加,因为由于历史原因,字符串字面值与 string 不是相同的类型 string 字符的操作
string.size() // 返回string.size_type,实际上是一个无符号整型数
string + string cs1 = "hello"; string cs2 = cs1 + "," + "world";
// string cs3 = "hello" + "world" + cs1; // 不能直接使用字面量相加
使用 for 循环迭代操作 string 中的字符,注意如果需要修改字符,需要使用引用,如下所示:
使用索引来访问字符串中的元素,需要注意的是,索引的类型也是 string::size_type
string fs("Hello,world");
for (auto &item : fs)
item = toupper(item);
std::cout
<<
fs << std::endl; // 输出HELLO,WORLD
在 c++ 标准中并不要求检查下标是否合法,如果一个下标超出了范围,则可能有不可预知的后果.vector 的初始化 c++ 提供多种初始化方式
vector 的长度: size 函数返回的是 vector 对象中元素的个数,类型是 vector:size_type 类型
vector<int> v1 = {1,2,3} // 拷贝初始化
vector<int> v2 {1, 2, 3} // 列表初始化
vector
<
int
>
v3(10, 1) //初始化10个1
试图用下标的形式访问一个不存在的元素将引发错误,不过这种错误不会被编译器发现,而是在运行时产生一个不可预知的错误.(例如缓冲区溢出 (buffer overflow))
迭代器
使用迭代器:使用 begin 和 end 函数返回迭代器,其中 begin 成员负责返回指向第一个元素的迭代器,end 成员函数返回指向容器(或 string 对象)尾元素的下一位置.
迭代器的 * iter 操作返回的是迭代器 iter 的引用??是否可以将 iter 本身理解为引用?
泛型编程:在 c++ 的 for 循环中,经常使用!= 来替代 java 中的 <= 作为判断是否跳出循环,这个是因为在 c++ 的标准库中,很多的容器类提供了!= 的运算符而没有 < 运算符,而很多时候又使用 iterator 来遍历容器.迭代器类型如 size_type 一样,我们一般不关心迭代器的具体类型.可以是 iterator 或者 const_iterator.使用新标准中提供的 cbegin() 和 cend(),返回 const 类型的 iteratorDereference 和 member 以 (*iter).empty 为例,*iter 的括号是必须的.如果没有括号的话将会被解析为 iter.empty member,而 iter 是一个迭代器没有 empty member.于此同时,c++ 中提供了 -> 运算符,这个就等于 (*it).
string sv = "some thing";
if(sv.begin() != sv.end()){
auto it = sv.begin();
*it = toupper(*it); // 使用*操作符解引用
}
使用 iterator 的不能使用 iterator.add() 来添加元素到容器中.
vector 和 string 提供了额外的操作,如下表所示:
使用 iterator 的减法返回有符号数类型的 difference_type
值得注意的是,迭代器只定义了减法运算而没有加法运算.
Array
字符数组由于字符串由'\0'结束,所以在初始化 char 型数组时需要比字面量空间更大.数组的初始化必须是一个初始化列表,不能用一个数组给另一个数组赋值,如下:
复杂的数组声明 int (*Parray)[10] = &arr; // Parray 是一个指针,指向大小为 10 的数组,数组类型为 intint (&arrRef)[10] = arr; //arrayRef 是一个引用,引用的对象是一个大小为 10 的 int 类型的数组
int[] a = {1, 2, 3};
int[]
a2 = a; // Error,不能使用数组赋值
在理解复杂的数组声明时,使用由内而外的理解方法去理解.
使用 auto 和 decltype 的类型:
Eg: int *(&array)[10] = ptrs; // array 是一个引用,引用的对象是 10 个 int 类型的指针 (Reference to an array of ten pointers) 通过下标访问数组通常使用 sizt_t 类型来定义数组的长度.数组和指针一般情况下,我们使用数组,编译器会自动将指针指向数组的第一个元素.
string nums[] = {"1", "2", "3";
string *p = &nums[0];
string
*
p2 = nums; //equivalent to p2 = &nums[0]
C++11 中引入了新的获取起始指针和尾指针的函数:begin() 和 end(),包含在 iterator header 两个指针相减的结果是 ptrdiff_t,类似于 size_t,定义在 cstddef header 中.可以使用指针来操作数组,参照如下例子:
int ia = [1, 2, 3];
auto ia2(ia2);
auto
ia2(
&ia[0]); // ia2的类型是int*
内建的数组下标可以是负数,而 vector 等类型的下标必须是无符号数
int *p = &ia[2]; // p points to the element indexed by 2
int j = p[1]; // p[1] is equivalent to *(p+1)
int
k
=
p[-2]; // equivalent to ia[0],可以使用减法指向之前的元素
c 语言中的 string 在 c 标准库中定义了一种字符串的 convention,以'\0'结尾,定义在 中(对应 c++ 中的 cstring)并且 c 标准库中提供的函数并不会校验传入的字符数组是否合法,这可能会引发一些问题.
对于指针来说,如果指针指向的不是同一个对象,那么指针之间的比较就没有意义.
c 语言中的字符串示例:
c 语言字符串和 c++ 字符串之间的互相使用:
const char ca1[] = "Hello";
const char ca2[] = "World";
if (ca1 < ca2) // undifined comparison.由于实际上是指向不同对象的两个指针做比较
可以使用 c 语言中的 null itermiated char 数组,用于初始化字符串
可以将 c 语言中的字符串用作操作数
c++ 中提供 c_str() 成员函数,用 string 初始化 char[]
第 4 章:表达式
基础
左值和右值一个左值表达式的求值结果是一个对象或者一个函数.当一个对象被用作右值时,使用的是对象的值,当一个对象用作左值时,使用的是对象的地址(在内存中的位置)值溢出
c++ 中支持使用:not 来作为非条件.
short short_value = 32767; // max value
short_value++; std: :cout << "short_value:" << short_value << std: :endl; // 发生了溢出,不同的系统上结果可能不一样(设置可能直接崩溃),值得记录的是,java的现象跟c++是一致的.运行结果是-32768
第 5 章: Statement
第 6 章: 函数
try 块
如果抛出了异常而且没有合适的 catch 处理,则最终执行 terminate 函数,具体的处理方法与系统相关
参数
const 参数在函数定义时,使用 const 和不使用 const 是一样的,因为使用 const,顶层的 const 会被忽略掉.关于 const 指针,引用参数的一些例子:
可变形参的函数 c++ 支持 initializer_list 形参,支持同种类型不同个数的参数同时支持可变长参数,但是一般仅仅用于和 c 语言交互的接口,因为很多对象不能正常的拷贝.
int i = 42;
const int *cp = &i; //正确,但是不能用cp改变i的值
const int &r = i; //正确,但是不能用r修改i的值
const int &r2 = 42; //正确,指向常量
int *p = cp; // 错误,指针类型不匹配
int &r3 = r; // 错误,类型不匹配
int
&
r4 = 42; // 错误,不能指向常量
不要返回局部对象的引用或指针,否则将指向不可用的地址空间或对象
返回数组指针 int *p[10] :10 个指针的数组 int (*p)[10]:一个指针,指向 10 个整数数组返回数组指针的方式:
typedef
声明一个返回数组指针的函数
使用尾指针返回类型 (auto func(int i)-> int(*)[10])
使用 decltype
引用与之类似
调试帮助
Assert 宏
assert 由 preprocessor 处理而不是编译器,所以使用 assert 时是直接使用.
NDEBUG 预处理变量
当使用#define NDEBUG 在文件开始处时,代码中的 assert 将不起作用,同时也可以在命令行中使用 - D NDEBUG 来设置.除此之外,还可以使用 NDEBUG 来编写一些根据 NDEBUG 的值判断是否执行的代码:
如果 NDEBUG 没有定义,则上面块中的代码将被执行.C++ 预处理器提供了一些用于调试的变量:
#ifndef NDEBUG
#endif
__func__ 当前函数
__FILE__ 当前文件
__LINE__ 当前行
DATE 编译日期
TIME 编译时间
重载
函数重载的选择:所有数学转换优先级都是相等的,如:
C++ 中不能将 const 类型的引用赋值给普通类型的引用(暂时可以助记为初始化常量引用时的限制没有普通引用多)
void test(float f);
void test(long l);
//调用test(1)时会出现ambiguous
函数指针定义类型: bool (*pf)(const string&) 可以直接使用函数指针来调用函数,而不需要进行解引用:
给函数指针赋值时,需要返回值类型和参数类型完全一致才可以赋值,函数指针之间不存在指针的转换函数指针指向重载函数时,需要指定具体使用的函数,通过确定的变量函数指针作为参数,可以直接使用函数作为参数,该函数实际上会作为一个指针来处理:
bool (*pf)(const string&);
pf("hello");
(*pf)("hello");
或者,也可以使用 typedef 和 decltype 来简化代码:
void test(bool pf(const string&));
void
test(
bool (
*
pf)(const string&));
//Func和Func2是函数类型
typedef bool Func(const string & );
typedef decltype(test) Func2;
返回指针类型
//Funcp和Funcp2是函数指针类型
typedef bool(*Funcp)(const string&);
typedef
decltype(test) *Funcp2;
例子:
//声明函数指针
using F = int(int*); // F是一个函数类型,而不是一个函数指针
using PF = int(*)(int*); // PF是函数指针
//定义函数指针
PF f1(int);
F *f1(int);
//上面的定义等于:
int (*f1(int))(int*);
//可以使用auto和decltype
auto
f1(
int)
-> int(*)(int*)
string::size_type sumLength(const string&, const string&){
}
string::size_type largerLength(const string&, const string&){
}
decltype(
largerLength)
*
getFunc(
const string&); //传入函数名来获取函数指针
来源: https://www.cnblogs.com/kode/p/8282379.html