第一部分、数据类型
字符型
- C和C++中的字符型变量值占用一个字节
- 字符型变量并不是把字符本身存放到内存中存储,而是将对应的ASCII编码放入存储单元
第二部分、运算符
算术运算符
// cout << "10/0=" << 10 / 0 << endl; // 报错 除数不能为 0 。
cout << "0.9 / 0.2 = " << 0.9 / 0.2 << endl; // 4.5
cout << "0.1 % 0.4 = " << 0.1 % 0.4 << endl; // 小数不能进行取模运算
// cout << "10%0 = " << 10 % 0 << endl; // 取模就是相除后取余数。既然相除都会报错,取模就更不用说了。
cout << "10%230 = " << 10 % 20 << endl; // 10
第三部分 指针
3.1 指针的定义和使用和所占的内存空间
int a = 10;
// 1、指针定义的语法: 数据类型 * 指针变量名
int* p;
// 让指针记录变量a的地址
p = &a;
cout << "a的地址为" << &a << endl;
cout << "指针p的地址为" << p << endl;
// 2、解引用
// 可以通过解引用的方法来找到指针指向的内存
// 指针前加 * 代表解引用,找到指针指向的内存中的数据
cout << " *p = " << *p << endl;
*p = 1000;
cout << "after define a = " << a << endl;
cout << " *p = " << *p << endl;
// 3、 指针所占的内存空间
// 一个指针变量(不论什么类型)占4个字节、64位系统下占8个字节
cout << "sizeof (int *)= " << sizeof(int *) << endl;
cout << "sizeof (float *)= " << sizeof(float *) << endl;
cout << "sizeof (float *)= " << sizeof(double *) << endl;
3.2 空指针和野指针
// 空指针用于给指针变量进行初始化
int* p = NULL;
// 空指针是不可以进行访问的
*p = 1000; // 报错
// 指针变量p指向内存地址编号为 0x1100的空间
int* p = (int*)0x1100;
// 访问野指针报错 (引发了异常: 读取访问权限冲突。)
cout << *p << endl;
空指针和野指针都不是我们申请的空间,不能随意访问
3.3 const修饰指针
// 1、const 修饰指针 --- 常量指针
int a = 10;
int b = 20;
const int* p1 = &a;
// 指针的指向可以改,指针指向的值不可以改
p1 = &b;
*p1 = 100; //error
// 2、const 修饰常量 --- 指针常量
int* const p2 = &a;
// 指针的指向不可以改,指针指向的值可以改
p2 = &b; //error
*p2 = 200;
const 修饰指针,则指针不可以改,修饰变量,则变量不可以改
第四部分、结构体
4.1结构体数组
struct Student {
string name;
int age;
int score;
};
struct Student studentList[3] = {
{"郑家崇",19,132},
{"郑家崇1",12,142},
{"郑家崇2",21,92}
};
cout << studentList[1].score << endl;
4.2结构体指针
struct Student {
string name;
int age;
int score;
};
struct Student stu = { "郑家崇",19,132 };
struct Student* p = &stu;
// 声明一个结构体指针,指向某个结构体。
// 指针通过 -> 可以访问成语
cout << p->score << endl;
4.3 结构体中const使用场景(限定结构体中的成员不可变更)
struct Student {
string name;
int age;
int score;
};
// 结构体中const使用场景(限定结构体中的成员不可变更)
void changeName(const Student* s)
{
s->name = "winston"; // 报错
cout << s->name << endl;
};
int main(void) {
struct Student stu = { "郑家崇",19,132 };
struct Student* p = &stu;
changeName(&stu);
}
第五部分、程序的内存模型
C++程序在执行时,将内存大方向划分为4个区域:
- 代码区:存放函数体的二进制代码.由操作系统进行管理的。
- 全局区:存放全局变量和静态变量以及常量。
- 栈区:由编译器自动分配释放,存放函数的参数值、局部变量等。
- 堆区:由程序员分配放.若程序员不释放,程序结束时由操作系统回收。
内存四区的意义:
不同区域存放的数据,赋予不同的生命周期,给我们最大的灵活编程。
5.1 程序运行后
栈区: 由编译器自动分配释放,存放函数的参数值,局部变量等。 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放。
int* fun() {
int a = 10;
return &a;
}
int main(void) {
int* p = fun();
cout << *p << endl;
cout << *p << endl; // 函数执行完成之后会自动被回收,所以这里就变得无法访问了。
}
堆区:
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区开辟内存
int *p = new int(10); //new方法返回一个内存地址
第六部分、引用
基本语法
数据类型 &别名 = 原名
int a = 10;
int &b = a;
b = 20;
cout << a << endl; // 20
引用的注意事项
- 必须要初始化
引用做为函数的参数
当我们试图去交换两个变量的时候,我们常用地址传递,当然我们现在可以使用引用传递。引用会对实参进行修改
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
};
int main(void) {
int a = 10;
int b = 30;
swap(a, b);
cout << "a=" << a << endl; // 30
cout << "b=" << b << endl;
}
常量引用
void change(const int& a) {
a = 12;
};
int main(void) {
int a = 10;
change(a);
cout << "a=" << a << endl;
}
防止在函数中操作影响实参
第七部分 函数高级
7.1 注意事项
- 如果函数声明中有默认参数,则函数定义就不能重复定义默认参数了。
7.2 函数重载的的注意事项
7.2.1 引用作为重载的条件
void func(int& a) {
cout << "func(int& a) 调用" << endl;
}
void func(const int& a) {
cout << "func(const int& a) 调用" << endl;
}
int main(void) {
int a = 10;
func(a);
func(10); // 整型常量
}
第八部分、类和对象
8.1 拷贝构造
class Person
{
public:
Person() {
cout << "person的无参构造函数调用" << endl;
};
Person(int a) {
cout << "Person的有参构造函数调用" << endl;
};
// 拷贝构造函数
Person(const Person &p) {
age = p.age;
cout << "Person的拷贝构造函数调用" << endl;
};
~Person() {
cout << "deconstruct" << endl;
}
private:
int age;
};
// 构造函数的调用
int main(void) {
Person p0(); // 调用默认构造函数的时候,不需要加上()。否则编译会当成这是一个函数去调用,然而并不存在这个函数
Person p1;
Person p2(10);
Person p3(p2);
Person(10); // 匿名对象 特点:当前执行结束后,系统会立即回收掉匿名对象
Person(p3); // 不要利用拷贝构造函数,去初始化匿名对象。编译器会认为是 Person(p3) === Person p3;导致重复定义错误。
// 隐式转换法
Person p4 = 10; // Person p4 = Person(10)
Person p5 = p4; // Person p4 = Person(p4)
}
以上代码有以下几点需要注意:
- 调用默认构造函数的时候,不需要加上()。否则编译会当成这是一个函数去调用,然而并不存在这个函数
- 匿名对象 特点:当前执行结束后,系统会立即回收掉匿名对象
- 不要利用拷贝构造函数,去初始化匿名对象。编译器会认为是 Person(p3) === Person p3; 导致重复定义错误
8.2 拷贝构造函数的调用时机
C++中拷贝构造函数调用时机通常有三种:
- 使用一个已经创建完毕的对象去初始化一个新的对象
Person p2(10);
Person p3(p2);
- 值传递的方式给函数参数传值
void doWork(Person p){
}
void test(){
Person p;
doWork(p)
}
8.3 构造函数的调用规则
默认情况下,C++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性值进行拷贝
构造函数调用规则如下:
- 如果存在自定义构造函数,C++不再提供默认无参构造,但会提供默认拷贝构造。
- 如果存在自定义拷贝构造函数,C++不会再提供其他构造函数。
- 如果存在定义有参构造函数,则C++将不再提供默认无参构造函数。(这意味这如果你不传参数去调用一个有参的构造函数,将会报错)
8.4 静态成员
静态成员就是在成员变量和成员函数前加上static关键字,称为静态成员
静态成员为:
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段进行内存分配
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享一个静态成员函数
- 静态成员函数只能访问静态成员变量
静态成员可以通过对象和类名进行访问
class Car {
public:
static int engine;
int static getEngine() {
return engine;
};
};
int Car::engine = 120;
int main(void) {
Car car;
Car car1;
car1.engine = 300;
cout << "car.engine" << car.engine << endl; // 300
cout << "Car::engine" << Car::engine << endl;
cout << "car.getEngine()" << car.getEngine() << endl;
cout << "car::getEngine()" << car::getEngine() << endl;
}
8.5 成员对象和函数的内存空间分配
class Person {
};
int main(void) {
Person p;
cout << "sizeof(p)" << sizeof(p) << endl; // 1
}
空对象占用一个字节的内存空间
class Person {
int a; // 非静态成员变量,属于类的对象上
static int b; // 静态成员变量,不属于类的对象上
void func(){} // 非 静态成员函数,不属于类的对象上
};
int main(void) {
Person p;
cout << "sizeof(p)" << sizeof(p) << endl; // 4
}
8.6 this的用途
- 当形参和成员变量同名时,可用this指针来区分
- 返回对象本身
class Person {
public:
Person(int age) {
this->age = age;
};
Person& PersonAddPerson(Person p) { // 返回对象本身进行链式调用
this->age = age + p.age;
return *this;
};
int getAge() {
return age;
};
private:
int age;
};
int main(void) {
Person p1(10);
Person p2(10);
p2.PersonAddPerson(p1).PersonAddPerson(p1);
cout << "p.age = " << p2.getAge() << endl;
}
8.7 const修饰成员函数
常函数:
- 在成员函数后加const,这个函数称为常函数。
- 常函数不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改。
常对象:
- 声明对象前加const,这个对象成为常对象
- 常对象只能调用常函数
class Person {
public:
void showAge() const{ // 修饰的时this指向,让指针的指向也不可以修改。
this->age = 10;
};
void func(){}
private:
mutable int age;
};
int main(void) {
const Person p;
p.showAge();
p.func(); // error 不兼容的限定符
};
8.8 友元
全局函数做友元
class Building {
public:
friend void myFriendFunc(Building* building);
Building() {
sittingRoom = "客厅";
bedRoom = "卧室";
};
string sittingRoom;
private:
string bedRoom;
};
void myFriendFunc(Building* building) {
cout << building->sittingRoom << endl;
cout << building->bedRoom << endl;
};
int main(void) {
Building building;
myFriendFunc(&building);
}
8.9 运算符重载
对已有的运算符重新进行定义重载,赋予其另一种功能,以适应不同的数据类型。
8.9.1 加号运算符重载
class Person
{
public:
// 成员函数重载
Person operator+(Person &p) {
Person temp;
temp.a = this->a + p.a;
temp.b = this->b + p.b;
return temp;
};
int a;
int b;
};
// 全局函数重载
Person operator+(Person& p1,Person& p2) {
Person temp;
temp.a = p1.a + p2.a;
temp.b =p1.b + p2.b;
return temp;
};
int main(void) {
Person p1;
p1.a = 10;
p1.b = 10;
Person p2;
p2.a = 10;
p2.b = 10;
Person p3 = p1 + p2;
cout << "p3.a = " << p3.a << "; p3.b = " << p3.b << endl;
}
8.9.2 左移运算符重载
class Person
{
public:
int a;
int b;
};
ostream & operator<<(ostream &cout,Person &p) {
cout << "p.a = " << p.a << "; p.b = " << p.b;
return cout; // 返回标准输出流,方便进行链式调用
}
int main(void) {
Person p1;
p1.a = 10;
p1.b = 10;
cout << p1 << endl;
}
8.9.3 赋值运算符重载
默认情况下,C++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性值进行拷贝
- 赋值运算符operator=,对属性值进行浅拷贝
class Person {
public:
Person(int age) {
this->age = new int(age);
};
/*
Person& operator=(Person &p){
if (age != NULL) {
delete age;
age = NULL;
};
age = new int(*p.age);
return *this;
};
*/
~Person() {
if (age != NULL) {
delete age;
age = NULL;
};
cout << "destructor" << endl;
};
int *age;
};
int main(void) {
Person p1(10);
cout << "p1的年龄为:" << *p1.age << endl;
Person p2(20);
p2 = p1;
}
默认赋值运算符,只是浅拷贝,对于存在堆区的数据只是指针拷贝。 然后在析构过程中,*age会在p1中被回收,后面p2在访问就报错了。
这就需要我们重载赋值运算符,重新开辟一块堆区的数据给新的对象。
8.9.4 关系运算符重载
class Person {
public:
int a;
int b;
Person(int xa, int xb):a(xa),b(xb) {
}
bool operator==(Person& p) {
if (this->a == p.a && this->b == p.b) {
return true;
};
return false;
}
};
int main(void) {
Person p1(10,12);
Person p2(10, 132);
if (p1 == p2) {
cout << "p1 == p2" << endl;
}
else {
cout << "p1 != p2" << endl;
}
}
8.10 继承
8.10.1 继承方式
继承的语法: class 子类 : 继承方式 父类
继承的方式一共有三种:
- 公共继承
- 保护继承
- 私有继承
其继承规则如下图:
8.10.2 继承同名成员函数的处理方式
当子类和父类中出现了同名的成员,如何分别范围呢?
- 子类可以直接访问到子类中的同名成员
- 子类对象加作用域可以访问到父类同名成员
class Base {
public:
void func() {
cout << "Base调用 " << endl;
};
};
class Son :public Base {
public:
void func() {
cout << "Son调用 " << endl;
}
};
int main(void) {
Son son;
son.func();
son.Base::func();
}
8.10.3 菱形继承问题
菱形继承就是说一个类继承两个父类,且两个父类又继承自同一基类。
菱形继承存在以下问题:
- 若在子类中访问祖父基类中的成员时,编译器不知道是哪个父基类的成员。
- 子类中会有两份个父基类的相同的数据,而子类往往只需要一份,这个问题如何解决?
这个时候我们就可以使用虚继承
虚继承其实就是继承基类的指针(vbptr - 虚基类指针)。
这是编译器不知道你访问的是哪个父级的age
class Animal {
public:
int age;
};
class Sheep :virtual public Animal {};
class Camel :virtual public Animal {};
class SheepCamel :public Sheep, public Camel {};
int main(void) {
SheepCamel sc;
sc.Sheep::age = 20;
sc.Camel::age = 30;
sc.age = 100;
}
使用虚函数就完美解决这个问题了
8.11 多态
多态是指调用同一个函数名,根据需要实现不同的功能。
- 编译时的多态性:静态多态(函数重载、运算符重载)
- 运行时的多态性:动态多态(虚函数)
函数重载和运算符重载实现的多态性属于静态多态性,在程序编译时就能决定调用的是哪个函数。静态多态性又称编译时的多态性。静态多态是通过函数重载、运算符重载实现的。
运行时的多态性是指在程序执行之前,根据函数名和参数无法确定应该调用哪一个函数,必须在程序的执行过程中,根据具体的执行情况来动态地确定。动态多态是通过虚函数(virtual function)实现的。
8.11.1 多态的基本语法
class Animal {
public:
virtual void speak() {
cout << "animal speak" << endl;
}
};
class Cat :public Animal {
public:
void speak() {
cout << "Cat speak" << endl;
}
};
void doSpeak(Animal& animal) {
animal.speak();
};
int main(void) {
Cat cat;
doSpeak(cat);
}
动态多态的满足条件
- 有继承关系
- 子类重写父类的虚函数
动态多态的使用
- 使用父类的指针或引用 指向子类对象
注意点
- 子类是重写父类的虚函数(注意不是重载,即所有参数和返回值都保持和父类一致)
8.11.2 纯虚函数和多态类
在多态中,通常父类的虚函数是毫无意义的,主要多是调用子类重写的内容
因为可以将虚函数改为纯虚函数
纯虚函数语法 : virtual 返回值类型 函数名 (参数列表) = 0
当类中有了纯虚函数,这个类就是抽象类
抽象类的特点:
- 无法实例化对象
- 子类必须重写父类的纯虚函数,否则也属于纯虚函数。
#include <iostream>
using namespace std;
class Base{
public:
void show(int a)=0;
};
int main() {
Base b; // 无法实例化对象
return 0;
}
8.11.3 虚析构和纯虚析构
动态多态使用时,如果在子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类中的析构代码。
class Base{
public:
Base(){
cout << "父类的构造函数调用" << endl;
};
~Base(){
cout << "父类的析构函数调用" << endl;
};
void speak() = 0 ;
};
class Son : public Base{
public:
Son(string name){
this->name = new String(name);
cout << "子类的构造函数调用" << endl;
};
~Son(){ // 不会被调用,子类堆区中的变量将无法被回收
if(name!=NULL){
delte name;
name = null;
}
cout << "子类的析构函数调用" << endl;
};
void speak(){
cout << *name << "在说话" << endl;
};
int *name;
}
int main(void){
Base *base = new Son();
base->speak();
}
解决方式:将父类中的析构函数改为虚析构或纯虚析构
virtual ~Base(){
cout << "父类的析构函数调用" << endl;
};
虚析构和纯虚析构的共性:
- 可以使父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构的区别:
- 如果是纯虚析构,则该类也属于抽象类,无法被实例化。
第九部分 文件操作
程序运行时产生的数据都属于临时数据,程序一旦结束就会被释放
通过文件可以将数据持久化
文件类型分为两种:
- 文本文件 - 文件以ASCII的形式存储在计算机中。
- 二进制文件 - 文件以二进制的形式存储在计算机中。
操作文件的三大类:
- ofstram 写操作
- ifstram 读操作
- fstream 读写操作
9.1 文本文件
9.1.1 写文件
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "my name is winston" << endl;
ofs.close();
9.1.2 读文件
ifstream ifs;
ifs.open("test.txt", ios::in);
if (ifs.is_open()) {
// 读数据
// 第一种方式
char buf[1024] = { 0 };
while (ifs >> buf)
{
cout << buf << endl;
};
// 第二种方式
char buf[1024] = { 0 };
while (ifs.getline(buf, sizeof(buf)))
{
cout << buf << endl;
}
*
// 第三种方式
string buffer;
while (getline(ifs,buffer)) {
cout << buffer << endl;
}
}
else {
cout << "文件路径不存在" << endl;
}
第十部分、模板
10.1 函数模板
template<typename T> //声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用类型数据
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
};
int main(void) {
int x = 10;
int y = 20;
// 两种方式使用函数模板
// 1.自动类型推倒
mySwap(x, y);
double a = 1.2;
double b = 2.3;
// 2.显示指定类型
mySwap<double>(a, b);
}
10.1.1 普通函数和函数模板的调用规则
普通函数和函数模板的调用规则:
- 如果函数模板和普通函数都可以被调用,优先调用普通函数。
void Myprint(int a) {
cout << "普通函数调用" << a << endl;
};
template <typename T>
void Myprint(T a) {
cout << "函数模板调用" << a << endl;
};
int main(void) {
Myprint(10); // 普通函数调用
}
- 可以通过空函数模板参数列表,强制调用函数模板。
void Myprint() {
cout << "普通函数调用" << endl;
};
template <typename T>
void Myprint() {
cout << "函数模板调用" << endl;
};
int main(void) {
Myprint<int>(); // 函数模板调用
}
- 函数模板可以发生重载
- 如果函数模板可以发生更好的匹配,优先调用函数模板。
10.1.2 模板的局限性
局限性:如果用户传入参数类型时自定义的数据类型,那么模板将无法处理。
template <typename T>
bool compare(T a, T b) {
if (a > b) {
return true;
};
return false;
};
class Person {
int a=10;
int b=20;
};
int main(void) {
compare(10, 12);
Person p1;
Person p2;
compare(p1, p2); // 报错,无法处理
}
为了解决这个问题,我们可以提供模板的重载,可以为这些特定的类型提供具体化的模板。
class Person {
public:
Person(int a, int b) {
this->a = a;
this->b = b;
};
int a;
int b;
};
template <class T>
bool mycompare(T &a, T &b) {
if (a > b) {
return true;
};
return false;
};
// 具体化模板,显示具体化的原型,以template<>开头,并通过名称来指出类型
// 具体化优先于常规模板
template<> bool mycompare(Person& p1, Person& p2) {
if (p1.a == p2.a && p1.b == p2.b) {
return true;
};
return false;
};
int main(void) {
Person p1(1,12);
Person p2(1,12);
mycompare(p1, p2);
}
10.2 类模板
10.2.1 类模板和函数模板的区别
类模板和函数模板的区别
- 类模板没有自动类型推导。
- 类模板在参数列表中可以有默认参数类型。
template<class T,class U=int> // 默认参数类型
class Person {
public:
Person(T name,U age) {
this->name = name;
this ->age = age;
};
void show() {
cout << "name = " << name << " ; age = " << age << endl;
};
T name;
U age;
};
int main(void) {
Person <string>p("winston", 18); // 没有自动类型推导,必须显示指定类型
p.show();
};
10.2.2 类模板中的成员函数的创建时机
类模板中的成员函数的创建时机
- 类模板中的成员函数在创建时才会去创建
10.2.3 类模板与继承
当类模板碰到继承的时候,需要注意以下几个问题:
- 当子类继承的父类是一个类模板的时候,子类在声明的时候,要指定父类中泛型的具体类型。
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定父类中的泛型类型,子类也需变成类模板
template <class T>
class Base {
T age;
};
class Son : public Base{}; // error 缺少类模板的参数列表
class Son : public Base<int> {}; // 正确
10.2.4 类模板成员函数的类外实现
template<class T, class U>
class Person {
public:
Person(T name, U age);
void show();
T name;
U age;
};
// 构造函数的类外实现
template<class T, class U >
Person<T,U>::Person(T name, U age) {
this->name = name;
this->age = age;
};
// 成员方法类外实现
template<class T, class U >
void Person<T, U>::show() {
cout << "name = " << this->name << " ; age = " << this->age << endl;
};
int main(void) {
Person <string,int>p("winston", 18);
p.show();
};
10.2.4 类模板分文件编写
当把类模板的声明和实现分开编写的时候,会报一个链接失败的错误。
原因是:类模板中的成员函数在创建时才会去创建。所以你直接引入一个.h的头文件时找不到成员的, 就会报错。
// Person.h
#pragma once
#include <iostream>
#include <string>
template<class T, class U>
class Person {
public:
Person(T name, U age);
void show();
T name;
U age;
};
// Person.cpp
#include <iostream>
#include "Person.h";
using namespace std;
template<class T, class U >
Person<T, U>::Person(T name, U age) {
this->name = name;
this->age = age;
};
template<class T, class U >
void Person<T, U>::show() {
cout << "name = " << this->name << " ; age = " << this->age << endl;
};
// main.cpp
#include "Person.h";
int main(void) {
Person <string,int>p("winston", 18); // linker error
p.show();
};
解决方法有两个:
- 直接包含.cpp源文件
- 将声明和实现放到同一个.hpp文件中。.hpp时约定俗成的后缀,并不强制。
10.2.5 类模板和友元
在类模板内部声明一个友元函数,可以将这个函数变成全局函数。
template <class T,class U>
class Person {
friend void printPerson(Person <T, U> p) {
cout << p.name << " is = " << p.age << endl;
};
public:
Person(T name,U age) {
this->name = name;
this->age = age;
};
private:
T name;
U age;
};
void test() {
Person<string,int> p("winston", 12);
printPerson(p);
};
int main() {
test();
}
第十一部分、STL初识
11.1 STL的诞生
- 大多数情况下,数据结构和算法都未能有一套标准,导致被迫从事大量的重复工作。
- 为了建立数据结构和算法的一套标准,诞生了STL。
11.2 STL的基本概念
- STL (Standard Template Library) 标准模板库
- STL广义上分为:容器(container),算法(algorithm),迭代器(iterator)
- 容器和算法之间通过迭代器进行无缝链接
- STL几乎所有的代码都采用了模板类或函数模板。
11.3 STL中的容器、算法、迭代器
STL容器就是运用数据结构的实现
11.3.1 vector存放内置数据类型
容器:vector
算法:for_each
迭代器:vector
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void callback(int value) {
cout << value << endl;
};
void test() {
// 创建一个vector容器,
vector<int> v;
v.push_back(10);
v.push_back(11);
// 第一种遍历方式
vector<int>::iterator itBegin = v.begin();
vector<int>::iterator itEnd = v.end(); // 最后一个元素的后一位
while (itBegin != itEnd) {
cout << *itBegin << endl;
itBegin++;
}
// 第二种遍历方式
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << endl;
};
// 第三种遍历方式 使用STL提供的遍历算法
for_each(v.begin(), v.end(), callback);
};
11.3.2 vector存放自定义数据类型
class Person {
public:
Person(int xa, int xb) :a(xa), b(xb) {};
int a;
int b;
};
void callback(Person p) {
cout << p.a << endl;
cout << p.b << endl;
};
void test2() {
vector<Person> v;
Person p1(10, 12);
Person p2(110, 112);
v.push_back(p1);
v.push_back(p2);
for_each(v.begin(), v.end(), callback);
};
第十二部分、STL常用容器
12.1 string容器
12.1.1 string的基本概念
本质:
- string 是C++风格的字符串,而string本质上是一个类。
string 和char *区别
- char * 是一个指针
- string是一个类,类内部封装了char *,管理这个字符串,是一个char * 的容器。
12.1.2 string的构造函数
构造函数原型
- string(); 创建一个空的字符串 例如:string:str;
- string(const char * s) 用字符串s区去初始化
- string(const string & str) 拷贝构造
- string(int m,char c) 使用n个字符c初始化
string str;
cout << str << endl;
const char* str1 = "hello world";
string str2(str1);
cout << "str2 = " << str2 << endl;
string str3(str2);
cout << "str3 = " << str3 << endl;
string str4(10, 'c');
cout << "str4 = " << str4 << endl;
12.1.3 string的基本操作
12.1.3.1 赋值操作
string str;
str = "str4"; // str4
str.assign("muname"); // muname
str.assign("muname",2); // mu
cout << str << endl;
12.1.3.2 拼接操作
string str;
str += "str"; // str
cout << str << endl;
str.append("append"); // strappend
cout << str << endl;
str.append("append",0,3); // strappendapp
cout << str << endl;
12.1.3.3 存取操作
string str="abcdefg";
cout << "str.size()" << str.size() << endl;
cout << "str[0]=" << str[0] << endl;
cout << "str.at(1)=" << str.at(1) << endl;
12.1.3.4 插入删除操作
string str="abcdefg";
str.insert(1, "www");
cout << str << endl; // awwwbcdefg
str.erase(0, 4);
cout << str << endl; // bcdefg
12.2 vector容器
12.2.1 vector容器的基本概念
功能
- vector数据结构和数组非常类似,也称为单端数组
vector与普通数组的区别
- 数组是静态空间,而vector可以动态扩展。
动态扩展
- 并不是在原有空间之后续接新空间,而是寻找一块更大的空间,然后将原数据拷贝至 新的空间,释放旧的空间。
12.2.2 vector容器的构造函数
- vector
v; 默认构造函数 - verctor(v.begin(),b.end()) 将v.begin(),b.end()区间中的元素拷贝给本身
- vector(n,ele) 用n个ele构造本身
- vector(const vector &vec) 拷贝构造函数
void callback(int value) {
cout << value << endl;
};
int main(void) {
vector<int> v(10,100);
for_each(v.begin(), v.end(), callback);
}
12.2.3 vector容量和大小
vector<int> v(10, 100);
cout << "v.size()=" << v.size() << endl;
cout << "v.capacity()=" << v.capacity() << endl;
v.resize(15, 12);
cout << "after resize capacity=" << v.capacity() << endl;
cout << "after resize size =" << v.size() << endl;
for_each(v.begin(), v.end(), callback);
12.2.4 vector数据存取
12.2.5 vector互换容器
当给一个vector进行resize操作时,其capacity并不会变化,这意味着vector所占的内存空间一直未改变。 这时候我们可以使用swap把更小的一块内存换给他,合理优化空间。
12.2.5 vector预留空间
当我们使用循环多次给vector增加数据的时候,由于vector一开始不知道需要开辟多少内存,所以当内存不够的时候, 又重新开辟空间而后把之前的值拷贝过去。预留空间就是告诉vector将有定量的数据插入。避免重新开辟内存导致的性能浪费。
vector<int> v1;
v1.reserve(10000);
12.3 deque容器
12.4 stack容器
12.4.1 stack容器的常用接口
stack<int> s;
s.push(1);
s.push(2);
cout << "栈的大小" << s.size() << endl;
// 遍历一个栈,只要不为空的时候一直查看栈顶,并执行出栈操作。
while (!s.empty())
{
cout << "栈顶元素为" << s.top() << endl;
// 出栈
s.pop();
};
12.5 queue容器
场景联想:排队打饭。
只有队头和对尾允许访问