比c增加的特性

bool, namespace, cin, cout, cerr(这三个不是关键字,是运算符重载,cout带缓冲,cerr不带缓冲)

引用

类似指针,修改变量是不用指针那么麻烦还要去找地址了,声明时就必须进行初始化。之后使用原变量,新引用都可以正常修改该地址下的值。

1
2
3
4
int a = 10;
int &b = a;
cout<<a<<" "<<b<<endl;
cout<<&a<<" "<<&b<<endl;

a,b的地址都一样,可以把b看作a的别名。

const 与 指针

基础情况

const放在 * 的左边

​ 指针变量所指向的数据不能通过指针变量改变,但指针变量是可以改变的

1
2
3
4
5
6
7
8
9
int main()
{
int a = 10;
int b = 20;
const int* p = &a; //等价于 int const* p = &a;
p = &b; //指针变量是可以改变
printf("%d\n", *p);
return 0;
}
const放在 * 的右边

指针变量不能改变,但指针变量所指向的对象是可以修改的

1
2
3
4
5
6
7
8
int main()
{
int a = 10;
int* const p = &a;
*p = 20;
printf("%d\n", a);
return 0;
}
const放在 * 的 左右边

​ const放在 * 的左右边,则指针变量不可被修改,且指针变量所指向的对象也不被修改,双重锁定。

部分内容来源自C语言const用法详解

归纳总结

判断谁是可变的,关键在于看*的位置,* 将整个语句划分成了两个部分,左边对应的是 int 型的,右边对应的是 int * 型的,看哪边有 const 哪边就不能变。如下

1
2
3
const int* p = &a;   //const 在* 左边, int类型不能变(不能给该int类型重新赋值,可以给指针类型重新赋值)
int* const p = &a; //const 在* 右边, int*类型不能变(可以给该int类型重新赋值,不能给指针类型重新赋值)
const int* const p = &a; //const 左右都有, int类型,int*类型都不能变 (不能给该int类型重新赋值,不能给指针类型重新赋值)

同理,如果是双重指针,两个 * 把整个语句划分成了三个部分,左边对应的是 int 型的,中间对应的是 int * 型的,右边对应的是 int ** 型的,看哪边有 const 哪边就不能变。看以下几个例子:

1
2
3
4
5
6
7
8
char ** p1;                     //    pointer to    pointer to    char 
const char **p2; // pointer to pointer to const char
char * const * p3; // pointer to const pointer to char
const char * const * p4; // pointer to const pointer to const char
char ** const p5; // const pointer to pointer to char
const char ** const p6; // const pointer to pointer to const char
char * const * const p7; // const pointer to const pointer to char
const char * const * const p8; // const pointer to const pointer to const char

补充一点,在c++中,const 放到类型前或类型后都是等价的

1
2
const int *p;    //same as below  const (int) * p
int const *q; // (int) const *p

const 与引用

引用似乎没有指针那么复杂,不会出现很多 & 的情况,只要在类型前加一个const 就可以限制引用更改变量了。

1
2
3
4
int a = 10;
const int &b = a;
b = 20; //compile error
a = 20;

有遗漏的之后再补充

强制类型转换

c++新增了个关键字 static_cast、const_cast、reinterpret_cast 和 dynamic_cast,他们是用于强制类型转换的。

  1. static_cast

    static_cast<待转换的变量类型>(变量),最常用的

  2. const_cast

    **const_cast**<待转换的变量类型>(变量指针或变量引用),能被转换的必须是变量指针或变量引用,它可以去除指针/引用的const属性,不能去除原变量的const属性。一种不规范的写法:
    
1
2
3
4
5
6
7
8
int main(void){
const int a = 5;
const int *p = &a;
int *q = const_cast<int *>(p);
*q = 1;
cout<<"a: "<<a<<", p: "<<*p<<", q: "<<*q<<endl;
return 0;
}

运行结果如下:a: 5, p: 1, q: 1,编译器无报错(其他编译器就不一定了),这种情况标准C++未规定,应避免这种行为。

它的一种用法是:定义一个非const变量/数组,将他以 const 类型传入到调用的函数中(防止修改值),以 const 类型返回后可以使用 const_cast 去掉他的const属性。

  1. reinterpret_cast

    主要有三个用法:

    • 改变指针或引用的类型
    • 将指针或引用转换为一个足够长度的整形
    • 将整型转换为指针或引用类型

​ reinterpret_cast 转换过程中仅仅是比特位的拷贝,使用时要特别谨慎,

  1. dynamic_cast

    和类的继承层次有关

内联函数

用于将一个函数声明为内联函数。在程序编译时,编译器会将内联函数调用处用函数体替换,这一点类似于 C语言中的宏扩展。

inline要放到函数定义前

1
2
3
4
5
6
7
void swap(int &a, int &b);
inline void swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}

分配内存和释放内存

c 中,申请内存和释放内存的函数包括 malloc,calloc, free,

c++中,申请内存和释放内存的操作符包括 new, new[],delete,delete[]。

1
2
3
4
int *p = new int;
int *q = new int[10];
delete p;
delete[] q;

C++异常

编译时可以解决语法错误,异常机制可以解决运行时错误,调试时可以解决逻辑错误。运行时错误如果放任不管,系统就会执行默认操作,终止程序,而异常机制给了程序一次重生的机会。一般用法:

1
2
3
4
5
6
7
try{
//可能抛出异常的语句,函数
//抛出异常后,程序直接跳转到catch,下面的语句不会被执行
}
catch(exceptionType variable){
//处理异常的语句
}

exceptionType是异常类型,可以说int, chat, float, bool,也可以是指针,字符串,结构体。c++语言本身抛出的异常,都是exception类或其子类的异常,也就是说抛出异常时,会创建一个 exception 类或其子类的对象。

抛出异常后,异常类型会和exceptionType相比较,相同则会把对象传给variable,否则不会进入这个catch,当然,可以有多个catch跟在try后,可以多次匹配,没匹配到后交给系统处理——终止运行。

来自C++异常类型的一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <string>
using namespace std;

class Base{ };
class Derived: public Base{ };

int main(){
try{
throw Derived(); //抛出自己的异常类型,实际上是创建一个Derived类型的匿名对象
cout<<"This statement will not be executed."<<endl;
}catch(int){
cout<<"Exception type: int"<<endl;
}catch(char *){
cout<<"Exception type: cahr *"<<endl;
}catch(Base){ //匹配成功(向上转型)
cout<<"Exception type: Base"<<endl;
}catch(Derived){
cout<<"Exception type: Derived"<<endl;
}

return 0;
}

本例中,我们定义了一个基类 Base,又从 Base 派生类出了 Derived。抛出异常时,我们创建了一个 Derived 类的匿名对象,也就是说,异常的类型是 Derived。

我们期望的是,异常被catch(Derived)捕获,但是从输出结果可以看出,异常提前被catch(Base)捕获了,这说明 catch 在匹配异常类型时发生了向上转型(Upcasting)

C++String类型

string 是 c++ 一个内建数据类型,可以替代 c 语言中的 char* 数组, 使用 string 时 需要包含头文件 <string>

几种定义方法

1
2
3
4
string s1;
string s2 = "string";
string s3 = s2;
string s4 (10, 's');
  • 变量 s1 只是定义但是没有进行初始化,系统会将默认值赋给 s1,默认值是“”(空字符串);
  • 变量 s2 在定义的时候就被初始化为了 “string”,与 C 风格的 char 型数组不同,string 类型的变量结尾是没有 ‘\0’ 的,string 类型的本质是一个 string 类,而我们定义的变量则是一个个的 string 类的对象;
  • 变量 s3 在定义的时候直接用 s2 进行初始化了,因此 s3 的内容也是 “string”;
  • 变量 s4 初始化为 10 个 ‘s’ 字符组成的字符串,也即 “ssssssssss”。

获取 string 长度时,使用length函数,如 s.length()

将 string 转为 char* 型字符串时,使用 c_str 函数,如 filename.c_str()

输入输出:

可以使用通用输入输出操作符处理,输入遇到空字符时,将值存到string

字符串连接

string 类型可以方便的使用 “+” 或 “+=” 进行连接。使用 “+” 时,操作符左右最少包含一个 string类型,另一半可以是 c 类型字符串或 char 字符;使用 “+=” 时,操作符右边可以是 string 类型, c 类型字符串或 char 字符

修改字符串

可以通过下标访问每个字符,起始下标从 0 开始。

erase 函数

删除子字符串。包含两个参数,第一个参数指明要删除的子字符串的起始下标,第二个是要删除子字符串的长度(不给出则删除至字符串结束)

insert 函数

插入字符串。包含两个参数,第一个参数指明插入位置(插入后第一个字符串是第几位),第二个参数可以是string 变量,也可以是 c 风格字符串。

replace 函数

可以替换 string 类型变量中的一个子字符串。有三个参数,第一个是待替换字符串 起始下标,第二个是 代替换字符串长度,第三个是要替换的字符串,可以是 string 或 c 风格字符串。

swap

将两个 string 类型变量的值互换。看不出什么用呢

提取子字符串 substr

可以提取 string 字符串 中的子字符串,有两个参数,第一个参数为待提取字符串的起始下标,第二个为代提取字符串的长度。

查找字符串

find

可以在字符串中查找子字符串中出现的位置。该函数有两个参数,第一个参数是待查找的子字符串,第二个参数是表示开始查找的位置,如果第二个参数不指名的话则默认从 0 开始查找,也即从字符串首开始查找。

找到返回下标,否则返回无穷大值。

rfind

与 find 类似,同样是在字符串中查找子字符串。不同的是,find() 函数是从第二个参数开始往后查找,而 rfind() 函数则是最多查找到第二个参数处,如果到了第二个参数所指定的下标还没有找到子字符串,则返回一个无穷大值。

find_first_of() 函数是用于查找子字符串和字符串共同具有的字符在字符串中出现的位置。

find_first_not_of() 函数则相反,它查找的是在 s1 字符串但不在 s2 字符串中的首位字符的下标,如果查找不成功则返回无穷大。

字符串比较

可以用 >= != == 等进行比较,对应字符大的值大。

1
2
3
string s1 = "abcde";
string s2 = "abcd";
cout<< (s1 > s2);

输出 1

c++模板

在定义模板类时,前面加上一句声明 template<class T>。模板类可以有一个类参数,但是可以有多个类参数。

实例化

通过<>指定一种数据类型,创建一个模板类的实例,如 array<int> a(10), array<double> a(10);

C++标准模板库(STL)

标准库包括三个部分: 容器,算法和迭代器。容器包括 vector, stack, queue, deque, list, set, map 等,STL 算法是对容器进行处理,比如排序、合并等操作。迭代器则是访问容器的一种机制。STL 容易有扩展性。

STL 基本容器可以分为

  • 序列示容器: list, vector, deque
  • 关联式容器: set, multiset, map, multimap

序列式容器可以像数组一样通过下标进行访问。关联式容器则是需要通过键值进行访问,关联式容器可以将任何类型的数据作为键值。

容器 类型 描述
vector 序列式容器 按照需要改变长度的数组
list 序列式容器 双向链表
deque 序列式容器 可以操作两端的数组
set 关联式容器 集合
multiset 关联式容器 允许重复的集合
map 关联式容器 图表
multimap 关联式容器 允许重复的图表

c++基本序列式容器

vector

可以在两端插入、删除数据的数组,它提供了丰富的成员函数,用于操作数据。在使用 vector 时,必须包含<vector>头文件。

函数

push_back() 从最后添加

insert(位置, 值) 从指定位置插入,会把插入位置及之后的所有元素右移,除了插入到尾部,其他效率都不高。

begin() 容器不为空返回容器第一个元素,否则返回容器尾部之后的元素。

end() 返回容器尾部之后的元素。

容器为空 begin end 指向同一个元素,反之不是

deque

函数与 vector 相同,几乎可以替换。区别在于vector 说到底是个数组,在非尾部插入元素都需要移动其它元素,而 deque 则不同,它是一个可以操作数组头部和尾部的数组,因此在头部或尾部插入或删除数据的效率都是一样的。当我们需要频繁在头部和尾部插入或删除数据,则 deque 优于 vector。

list

没有重载下标操作符,只能通过迭代器进行访问,

1
2
list <string>::iterator       //不需要修改容器内容时
list<string>::const_iterator //需要修改容器内容时

获得 迭代器 iter 时,通过 *iter 就获得了容器中的元素。

list 容器是一个双向链表,因此在容器中的任何位置插入元素,都不需要移动其它元素,因此执行效率是稳定的。

c++ 基本关联式容器

基本的关联式容器主要有:set、multiset、map 和 multimap

set 可以理解为我们数学中的集合,它可以包含 0 个或多个不重复的数据。

map 也是一种集合,它同样可以包含多个元素,每一个元素由一个键值和一个与键值相关联的值组合而成,这样的元素又称键值对(<key, value>)

set 容器会根据各个元素值的大小进行排序,默认是升序排序,map 容器会根据各个元素键的大小进行排序, 默认升序,set 元素不能重复, map 键也不能重复,有 multi 的可以重复

map set 内部实现是红黑树,插入数据时就自动做了排序,不支持后期sort排序,因此,在定义对象时就需要指定其排序规则。

set

insert 函数,可以像之前容器一样 s.insert(s.begin(), 9);也可以省略掉插入位置,s.insert(6);

find 函数, 查找是否set 容器中 是否包含指定的键值,存在则返回指向该键值的迭代器,否则返回end()。

map

声明示例:map<char, int> m, char 表示键的类型, int 表示值的类型。使用迭代器时,使用 iter->first 访问键, 使用 iter->second 访问值

C++容器适配器

用基本容器实现的一些新容器,可以描述更高级的数据结构。包括三类:stack、queue 和 priority_queue,stack 与 栈对应,queue 与 队列对应,priority_queue 是带优先级的队列,其元素可以按照某种优先级顺序进行排列。

stack

push 入栈,pop 出栈, top 返回栈顶元素但不删除他,empty 判断栈是否为空,为空返回 true

queue

stack衍生自 deque,queue 衍生自deque。queue 没有栈顶这个说法,不过可以用 front 访问列头的元素但不会删除元素。他也有 push,pop,empty函数。

priority_queue

使用priority_queue 时头文件只需要包含 queue 就可以了。

可以用 push pop 函数来插入或删除元素,最前面是队列头, top也可以获取队列头的元素。默认顺序是从大到小排列的。

C++ STL算法

stl 提供了很多操作容器的算法,大致分为:排序,搜索,集合运算,数值处理,拷贝等。这些算法的实现是采用函数模板来实现的,函数模板类似于类模板。对于 STL 算法而言,算法是一样的,只是所处理的容器不同,只要使用合适的迭代器,就可以直接用算法操作容器了。

如果需要使用 STL 算法的话,需要在头文件中包括<algorithm>文件,

generate

1
2
3
vector < int > num ( 10 );
//生成随机数字,填充num
generate( num.begin(), num.end(), rand );

前面两个参数均为迭代器,分别指向开头和结尾,第三个参数返回值也为整型,为要赋给 num 的值

replace_if

1
2
//将其中的奇数全部替换我0
replace_if( num.begin(), num.end(), odd, 0 );

前面两个参数还是两个迭代器,第三个参数为返回 bool 类型的函数,为true时,将值替换为第四个值,否则不替换

sort

1
2
//从大到小排序
sort( num.begin(), num.end(), compare );

前面两个参数仍然是迭代器,第三个参数是可选的,默认情况下 sort() 将会以升序进行排序。第三个参数为返回一个 bool 类型的函数。

for_each

1
for_each( num.begin(), num.end(), display );

前面两个参数仍然是两个迭代器,将容器中的每个值传给display处理

除了对象相关的,其他最最基础内容已经总结完了