C++(二)
Pointers
首先,我们应该理解指针是地址的变量。与其他类型的变量一样,指针具有自己的值,该值是内存中的地址。
有一个运算符可以返回变量或对象的地址。如果我们有,我们可以通过 获得 的地址。因此,我们可以声明一个指针并为其分配一个地址。
&int num = 10;num&num
int num = 10; int * p1 = NULL, * p2 = NULL; // declaration two pointers, initialized to 0 p1 = # // take the address of num, assign to p1 p2 = # // take the address of num, assign to p2 *p1 = 20; // assign 20 to num *p2 = 30; // assign 20 to num

在前面的源代码中,声明了两个指针和。它们都被分配了 的地址。这意味着两个指针指向同一内存,并且两个指针认为在内存的该位置有一个。
此处的另一个运算符用于指针取消引用。它可以取消引用指针 a 以访问对象。以下两行是等效的,因为它是指向 的指针。
*p1 = 20; num = 20;
指针不仅可以指向一些基本类型,例如 ,它们还可以指向结构或对象的对象,甚至是函数。在下面的代码中,声明并初始化了一个结构。然后将其地址初始化为指针 。结构和指针如图所示。int float stu pStu
struct Student { char name[4]; int born; bool male; }; //declare and initialize a structure Student stu = {"Yu", 2000, true}; //assign the address of stu to pStu Student * pStu = &stu; //change members of the structure through pointer pStu strncpy(pStu->name, "Li", 4); pStu->born = 2001; (*pStu).born = 2002; pStu->male = false;
printf("Address of stu: %p\n", pStu); //C style
cout << "Address of stu: " << pStu << endl; //C++ style
cout << "Address of stu: " << &stu << endl;
cout << "Address of member name: " << &(pStu->name) << endl;
cout << "Address of member born: " << &(pStu->born) << endl;
cout << "Address of member male: " << &(pStu->male) << endl;

Constant pointers
如果类型限定符放在基本类型(如 )之前,则 的值在其初始化后无法更改。const const int num = 1;
如果 放在指针的类型名称之前(如以下示例所示),则无法更改指针指向的值。
int num = 1; //You cannot change the value that p1 points to through p1 const int * p1 = # *p1 = 3; //error num = 3; //okay
但是您可以更改指针本身。
p1 = &another; //okay
const也可以放在 和 名称之间。如果是这样,指针将始终指向该内存,无法指针指向其他位置。但是该内存中的值可以更改。
//You cannot change value of p2 (address) int *const p2 = # *p2 = 3; //okay p2 = &another; //error
如果按如下方式使用两个,则地址和值都不能更改。
//You can change neither const int* const p3 = # *p3 = 3; //error p3 = &another; // error
int num = 1; //You cannot change the value that p1 points to through p1 const int * p1 = # *p1 = 3; //error num = 3; //okay
第三章 字符串向量和数组
for(declaration: expression)
statement
declaration:负责定义一个变量
expression:一个对象
int main() { string str("some string"); for (auto c : str) cout << c << endl; cin.get(); }

int main() { string s("Hello World!! "); decltype(s.size()) punct_cnt = 0; for (auto c : s) { if (ispunct(c)) ++punct_cnt; } cout << punct_cnt << "punctuation characters in" << s << endl; cin.get(); }

冒泡排序
#include <iostream> using namespace std; //冒牌排序函数 void bobbleSort(int *arr, int len) { for (int i = 0; i < len - 1; i++) { for (int j = 0; j < len - i - 1; j++) { if(arr[j] > arr[j+1]) { int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } } //打印数组 void printArray(int *arr, int len) { for (int i = 0; i < len; i++) cout << arr[i] << endl; } int main() { int arr[] = {1, 2, 54, 34, 33, 56, 8, 7}; int len = sizeof(arr) / sizeof(arr[0]); bobbleSort(arr, len); printArray(arr, len); system("pause"); return 0; }
·
结构体案例1
#include <iostream> #include <ctime> using namespace std; struct Student { string sName; int score; }; struct Teacher { string tName; struct Student sArray[5]; }; //给老师和学生赋值的函数 void allocateSpace(struct Teacher tArray[], int len) { string nameSeed = "ABCDE"; //给老师开始赋值 for (int i = 0; i < len; i++) { tArray[i].tName = "Teacher_"; tArray[i].tName += nameSeed[i]; for (int j = 0; j < 5; j++) { tArray[i].sArray[j].sName = "Student_"; tArray[i].sArray[j].sName += nameSeed[j]; int random = rand() % 61 + 40; // [rand() % 61] 表示 0~59 40 ~100 tArray[i].sArray[j].score = random; } } } //打印所有信息 void printInfo(struct Teacher tArray[], int len) { for (int i = 0; i < len; i++) { cout << "老师姓名:" << tArray[i].tName << endl; for (int j = 0; j < 5; j++) { cout << "\t学生姓名:" << tArray[i].sArray[j].sName << " 考试分数" << tArray[i].sArray[j].score << endl; } } } int main() { //随机种子 srand((unsigned int)time (NULL)); //1.创建3名老师的数组 struct Teacher tArray[3]; //2.通过函数给3名老师的信息赋值,并给老师带的学生信息赋值 int len = sizeof(tArray) / sizeof(tArray[0]); allocateSpace(tArray, len); //3.打印所有老师及所带学生的信息 printInfo(tArray, len); cin.get(); }
堆区
堆区数据由程序员管理和释放
堆区数据利用new关键字进行开辟内存。
C++中的引用的注意事项
int a = 10; int &b = a;
1.引用必须要初始化
int &c; //错误,引用必须初始化
int &c = a; //一旦初始化后,就不可以更改
c = b; //这是赋值操作,不是更改引用
void mySwap03(int& a, int&b) { int temp = a; a = b; b = a; } int main() { int a = 10; int b = 10; mySwap03(a, b); cout << "a:" << a << " b:" << b << endl; }
2.5 引用的本质
本质:引用的本质在C++内部实现是一个指针常量
指针常量:int * const p = &a;
p = &b; //错误
*p = 100; //正确
类
class Circle { public: //设置半径 void setR(int r) { m_R = r; } //获取半径 int getR() { return m_R; } //设置圆心 void setCenter(Point center) { m_Center = center; } //获取圆心,返回的事是一个类 Point getCenter() { return m_Center; } private: int m_R;//半径 Point m_Center;//圆心 };
circle.h circle.cpp point.h point.cpp circle_spot.cpp
类和对象-对象特征-初始化列表
#include <iostream> using namespace std; //初始化列表
class Person { public: //传统初始化操作 //Person(int a, int b, int c) //{ // m_A = a; // m_B = b; // m_C = c; //} //初始化列表初始化属性 Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) { } int m_A; int m_B; int m_C; }; void test() { Person p(30,2,21); cout << "m_A = " << p.m_A << endl; cout << "m_B = " << p.m_B << endl; cout << "m_C = " << p.m_C << endl; } int main() { test(); cin.get(); return 0; }
类和对象-对象特性-类对象作为类成员
#include <iostream> using namespace std; //类对象作为类成员 //手机类 class Phone { public: Phone(string pName) { m_PName = pName; cout << "Phone构造函数" << endl; } //手机品牌 string m_PName; }; class Person { public: //Phone m_Phone = pName 隐式转换法 Person(string name, string pName) :m_Name(name), m_Phone(pName) { cout << "Person构造函数" << endl; } //姓名 string m_Name; //手机 Phone m_Phone; }; void test() { Person p("ccx", "Huawei"); cout << p.m_Name << "拿着:" << p.m_Phone.m_PName << endl; } int main() { test(); cin.get(); }

当其他类对象作为本类成员,构造时候先构造对象,在构造自身。析构的顺序?析构与构造相反。
4.2.8 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
-
静态成员变量
-
所有对象共享同一份数据
-
在编译阶段分配内存
-
类内声明,类外初始化
-
-
静态成员函数
-
所有对象共享同一个函数
-
静态成员函数只能访问静态成员变量
#include <iostream> using namespace std; //静态成员变量 class Person { public: static int m_A; //静态成员变量也是有访问权限的 private: static int m_B; }; int Person::m_A = 100; int Person::m_B = 200; void test() { Person p; cout << p.m_A << endl; //100 Person p2; p2.m_A = 200; cout << p.m_A << endl; //200 } void test02() { //1.通过对象 //Person p; //cout << p.m_A << endl; //2.通过类名 cout << Person::m_A << endl; } int main() { //test(); test02(); cin.get(); }
33类和对象-对象特征-this指针的用图
#include <iostream> using namespace std; class Person { public: Person(int age) { //this指针指向 被调用的成员函数 所属对象 this->age = age; }
---如果不加&,这里是值传递,那么返回的是p2的值。p2.PersonAddAge(p1)整体是一个新的对象 Person& PersonAddAge(Person& p) { this->age += p.age; //this指向p2的指针,而*this指向的就是p2这个对象本身 return *this; } int age; }; void test() { Person p1(10); cout << "p1的年龄为:" << p1.age << endl; } void test02() { Person p1(10); Person p2(20); p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(10);
cout << "p2的年龄为:" << p2.age << endl; } int main() { test(); test02(); cin.get(); }
37 类和对象-友元-全局函数做友元
#include <iostream> using namespace std; //建筑物类 class Building { //goodGay全局函数是Building好朋友,可以访问Building中私有成员 friend void goodGay(Building* building); public: Building() { m_SittingRoom = "客厅"; m_BedRoom = "卧室"; } public: string m_SittingRoom; //客厅 private: string m_BedRoom; //卧室 }; //全局函数 void goodGay(Building* building) { cout << "好朋友全局函数正在访问:" << building->m_SittingRoom << endl; cout << "好朋友全局函数正在访问:" << building->m_BedRoom << endl; } void test() { Building building; goodGay(&building); } int main() { test(); cin.get(); }
38 类和对象-友元-成员函数做友元
40.C++运算符重载-加号运算符重载
#include <iostream> using namespace std; //加号运算符重载 class Person { public: //1.成员函数重载+号 //Person operator+(Person& p) //{ // Person temp; // temp.m_A = this->m_A + p.m_A; // temp.m_B = this->m_B + p.m_B; // return temp; //} int m_A; int m_B; }; //2.全局函数重载+号 Person operator+(Person& p1, Person& p2) { Person temp; temp.m_A = p1.m_A + p1.m_B; temp.m_B = p1.m_B + p2.m_B; return temp; } //函数重载的版本 Person operator+(Person& p1, int num) { Person temp; temp.m_A = p1.m_A + num; temp.m_B = p1.m_B + num; return temp; } void test01() { Person p1; p1.m_A = 10; p1.m_B = 10; Person p2; p2.m_A = 10; p2.m_B = 10; //成员函数重载本质调用 //Person p3 = p1.operator+(p2); // 全局函数重载本质调用 //Person p3 = operator + (p1, p2); //Person p3 = p1 + p2; //运算符重载 也可以发生函数重载 Person p3 = p1 + 10; cout << "p3.m_A = " << p3.m_A << endl; cout << "p3.m_B = " << p3.m_B << endl; } int main() { test01(); cin.get(); }
继承方式
6.1 基类中的私有继承都不能继承
6.2 公有继承 父类中的protected在子类中是protected
父类中的public在子类中是public
保护继承 父类中的protected在子类中是protected
父类中的public在子类中是protected
私有继承 父类中的protected在子类中是private
父类中的public在子类中是private
6 向上类型和向下类型转换
基类转派生类
向下类型转换 不安全
派生类转基类
向上类型转换 安全
如果发生多态 都安全
#### 1.2.5 普通函数与函数模板的调用规则
调用规则如下:
1. 如果函数模板和普通函数都可以实现,优先调用普通函数
2. 可以通过空模板参数列表来强制调用函数模板
3. 函数模板也可以发生重载
4. 如果函数模板可以产生更好的匹配,优先调用函数模板
模板局限性
模板不能解决所有的类型
如果出现不能解决的类型,可以通过第三地具体话来解决问题
template<> 返回值 函数名<具体类型>(参数)
#include <iostream> using namespace std; class Person { public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age; }; template<class T> bool myCompare(T &a, T&b) { if (a == b) { return true; } return false; } //通过具体化自定义数据类型 //如果具体化能过优先匹配,那么就选择具体化 template<> bool myCompare<Person>(Person& a, Person& b) { if (a.m_Age == b.m_Age) { return true; } return false; } void test() { int a = 10; int b = 20; int ret = myCompare(a, b); cout << "ret = " << ret << endl; Person p1("Tom", 10); Person p2("Jerry", 10); int ret2 = myCompare(p1, p2); cout << "ret2 = " << ret2 << endl; } int main() { test(); cin.get(); }
4 类模板
1.写法 template<T...>紧跟着类
2.与函数模板区别,可以有默认类型参数
3.函数模板可以进行自动类型推导,而类模板不行
6 类模板做函数参数
#include <iostream> using namespace std; //类模板 template <class NameType, class AgeType = int> class Person { public: Person(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl; } NameType m_Name; AgeType m_Age; }; //1 指定传入类型 void doWork(Person<string, int>& p) { p.showPerson(); } void test1() { Person <string, int>p("hhh ", 10); doWork(p); } //2 参数模板化 template<class T1, class T2> void doWork2(Person<T1, T2> &p) { cout << typeid(T1).name() << endl; cout << typeid(T2).name() << endl; p.showPerson(); } void test2() { Person <string, int> p("ccc", 18); doWork2(p); } //3 整体类型化 template<class T> void doWork3(T& p) { p.showPerson(); } void test3() { Person <string, int> p("cic", 19); doWork3(p); } int main() { //test1(); test2(); cin.get(); }
类型转换
1 静态转换 static_cast
2 使用方式 static_cast<目标类型>(原始数据)
3 可以进行基础数据类型转换
4 父与子类型转换
5 没有父子关系的自定义
异常的基本语法
2.1 三个关键字 try throw catch
2.2 try 试图执行一段可能会出现异常的代码
2.3 throw 出现异常后 抛出 catch 捕获异常
2.5 如果想捕获其他类型的异常 catch(...)
2.6 如果捕获到的异常不想处理,想继续向上抛出 throw
2.7 异常必须有人处理,如果没有处理,程序会自动调用 terminate函数,使程序中断
2.8 可以抛出一个自定义类型的异常MyException
3 栈解旋:从try代码块开始起,到throw抛出异常前。所有栈上的对象都被释放掉,释放的顺序和构造的顺序是相反的。
4 异常的接口声明
4.1 如果只允许函数体中抛出某种类型的异常,可以使用异常的接口声明
4.2 void func() throw(类型),如果throw(空),代表不允许抛出异常

5 异常的生命周期
5.1 MyException e会调用拷贝构造
5.2 MyException &e 引用方式 接受 建议用这种方式节省开销
5.3 MyException *e 指针方式 接受 抛出 &MyException();匿名对象,对象被释放掉, 不可以再操作e了
5.4 MyException *e 指针方式 接受 抛出 new MyException(); 堆区创建的对象 记得手动释放 delete e;
5.5 建议使用引用的方式去接受对象
6 异常的多态使用
#include <iostream> using namespace std; //异常基类 class BaseException { public: virtual void printError() = 0; }; //空指针 异常 class NULLPointException :public BaseException { public: virtual void printError() { cout << "空指针异常" << endl; } }; //越界异常 class OutofException :public BaseException { public: virtual void printError() { cout << "越界异常" << endl; } }; void doWork() { throw NULLPointException();
throw OutofException(); } void test() { try { doWork(); } catch (BaseException &e) { e.printError(); } } int main() { test(); }
6.1 提供基类异常类,BaseException c纯虚函数 virtual void printError() = 0
6.2 提供两个子类 继承 异常基类 重写父类中的纯虚函数·
6.3 测试 利用父类引用 去接受子类对象 实现多态
6.4抛出那种异常 就打印哪种异常中的printError函数
7 使用系统标准异常

7.1 标准异常头文件 #include<stdexcept>
7.2 使用系统异常类 out_of_range("char *")
7.3 捕获 catch(exception &e) {cout << e.what(); };
8 编写自己的异常类
8.1 提供自己的越界异常类
8.2 class myoutOfRange : public exception
8.3 重写 what
8.3.1 const char *what() const
8.4 内维护一个string m._errorInfo
8.5 char和string之间的转换
8.5.1 char*转 stringstring.的有参构造string(char *)u
8.5.2 string 转const char* .c.str();
8.6 测试利用多态打印我自己的异常类
9 标准的输入流
cin.get() 缓冲区读取一个字符
cin.get(两个参数) 不读换行符
cin.getline() 读取换行 扔掉
cin.ignore() 忽略(N) N代表忽略字符数
cin.peek() 偷窥 偷看1个字符然后放回去
cin.putback() 把字符放回缓冲区
cin.fail() 缓冲区的标志位 0代表正常 1代表异常
cin.clear() cin.sync() 重置标志位 并且刷新缓冲区
10 标准输出流


#include <iostream> using namespace std; /* cout.put() //向缓冲区写字符 cout.write() //从buffer中写num个字节到输出流中。 */ void test() { //cout.put('a').put('b').put('c'); char buf[] = "hello world"; cout.write(buf, strlen(buf)); } void test2() { int num = 99; cout.width(20);//预留20空间 cout.fill('*');//填充 cout.setf(ios::left);//左对齐 cout.unsetf(ios::dec);//卸载十进制 cout.setf(ios::hex); //安装十六尽职 cout.setf(ios::showbase); //设置显示进制 基数 cout.unsetf(ios::hex); //卸载十六尽职 cout.setf(ios::oct); //安装八进制 cout << num << endl; } int main() { test2(); system("pause"); }

11 文件读写操作
11.1 读写头文件 fstream
11.2 写 ofstream ofs
11.2.1 ofs.open指定打开方式 ios::out | ios::trunk
11.2.2 判断是否打开成功 ofs.is_open
11.2.3 ofs<< "aaa"
11.2.4 关闭流对象
11.3 读
11.3.1 ifstream ifs
11.3.2 指定打开方式 ios::in
11.3.3 判断是否打开成功 is_open
11.3.4 三种方式 可以读取文件中的数据
11.3.5 关闭流对象 ifs.close()
#include <iostream> using namespace std; //文件读写的头文件 #include <fstream> void test() //写 { // 参数 文件路径 打开方式 //ofstream ofs("./test.txt", ios::out | ios::trunc); ofstream ofs; ofs.open("./test.txt", ios::out | ios::trunc); //判断是否打开成功 if (!ofs.is_open()) { cout << "文件打开失败" << endl; return; } //写文件 ofs << "姓名:ccx" << endl; ofs << "年龄:18" << endl; ofs << "性别:girl" << endl; //关闭流对象 ofs.close(); } void test2() //读 { ifstream ifs; ifs.open("./test.txt", ios::in); //设置打开方式 if (!ifs) { cout << "文件打开失败" << endl; return; } //第一种方式 char buf[1024] = { 0 }; 将每行输入读入到缓冲区中 while (ifs >> buf)//按行读取,直到读到文件尾 { cout << buf << endl; } //第二种方式 char buf[1024] = { 0 }; while (!ifs.eof()) { ifs.getline(buf, sizeof(buf)); cout << buf << endl; } //第三种方式 单个字符读取 char c; while ((c = ifs.get()) != EOF) { cout << c; } //关闭流对象 ifs.close(); } int main() { test2(); system("pause"); }
2 容器算法迭代器初识
#include <iostream> using namespace std; #include <vector> #include <algorithm> //普通指针也是一种迭代器 void test() { int arr[5] = { 1, 5, 2, 7, 3 }; int * p = arr; for (int i = 0; i < 5; i++) { //cout << arr[i] << endl; cout << *(p++) << endl; } } void myPrint(int val) { cout << val << endl; } //内置属性类型 void test2() { vector<int>v; //声明一个vector容器 //向容器中添加数据 v.push_back(10); v.push_back(15); v.push_back(2); v.push_back(3); //通过迭代器 可以遍历容器 //每个容器都有自己专属的迭代器 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; } //第三种遍历方式 利用系统提供算法 for_each(v.begin(), v.end(), myPrint); } //自定义数据类型 class Person { public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age; }; void test3() { vector<Person> v; Person p1("ccx", 18); Person p2("cc", 18); Person p3("zky", 18); Person p4("xcl", 50); v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); //遍历 for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) { //*it(Person类型) it(指针) cout << "姓名: " << (*it).m_Name << "年龄: " << it->m_Age << endl; } } //存放自定义数据类型的指针 void test4() { vector<Person*> v; Person p1("ccx", 18); Person p2("cc", 18); Person p3("zky", 18); Person p4("xcl", 50); v.push_back(&p1); v.push_back(&p2); v.push_back(&p3); v.push_back(&p4); for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++) { //*it(Person*类型) cout << "姓名: " << (*it)->m_Name << "年龄: " << (*it)->m_Age << endl; } } //容器嵌套容器 void test5() { vector<vector <int>> v; //类似二维数组 vector<int>v1; vector<int>v2; vector<int>v3; for (int i = 0; i < 10; i++) { v1.push_back(i); v2.push_back(i+10); v3.push_back(i+100); } //将小容器插入到大容器中 v.push_back(v1); v.push_back(v2); v.push_back(v3); for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++) { //*it (vector<int>) for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++) { //*vit (int) cout << *vit << " "; } cout << endl; } } int main() { test5(); system("pause"); return EXIT_SUCCESS; }
2.1 vector<类型>v 容器
2.2 尾插 v.push_back()
2.3 起始迭代器 v.begin() 指向容器中第一个元素
2.4 结束迭代器v.end() 指向容器中最后一个元素下一个位置
2.5 for_each 遍历 引入头文件 #include <algorithm>
2.6 练习内置数据类型、自定义数据类型、容器嵌套容器
3 string 容器


#include <iostream> using namespace std; #include <stdexcept> #include <vector> /* * string构造函数 string();//创建一个空的字符串 例如: string str; string(const string& str);//使用一个string对象初始化另一个string对象 string(const char* s);//使用字符串s初始化 string(int n, char c);//使用n个字符c初始化 string 基本赋值操作 string& operator=(const char* s);//char*类型字符串 赋值给当前的字符串 string& operator=(const string &s);//把字符串s赋给当前的字符串 string& operator=(char c);//字符赋值给当前的字符串 string& assign(const char *s);//把字符串s赋给当前的字符串 string& assign(const char *s, int n);//把字符串s的前n个字符赋给当前的字符串 string& assign(const string &s);//把字符串s赋给当前字符串 string& assign(int n, char c);//用n个字符c赋给当前字符串 string& assign(const string &s, int start, int n);//将s从start开始n个字符赋值给字符串 string& operator+=(const string& str);//重载+=操作符 string& operator+=(const char* str);//重载+=操作符 string& operator+=(const char c);//重载+=操作符 string& append(const char *s);//把字符串s连接到当前字符串结尾 string& append(const char *s, int n);//把字符串s的前n个字符连接到当前字符串结尾 string& append(const string &s);//同operator+=() string& append(const string &s, int pos, int n);//把字符串s中从pos开始的n个字符连接到当前字符串结尾 string& append(int n, char c);//在当前字符串结尾添加n个字符c int find(const string& str, int pos = 0) const; //查找str第一次出现位置,从pos开始查找 int find(const char* s, int pos = 0) const; //查找s第一次出现位置,从pos开始查找 int find(const char* s, int pos, int n) const; //从pos位置查找s的前n个字符第一次位置 int find(const char c, int pos = 0) const; //查找字符c第一次出现位置 int rfind(const string& str, int pos = npos) const;//查找str最后一次位置,从pos开始查找 int rfind(const char* s, int pos = npos) const;//查找s最后一次出现位置,从pos开始查找 int rfind(const char* s, int pos, int n) const;//从pos查找s的前n个字符最后一次位置 int rfind(const char c, int pos = 0) const; //查找字符c最后一次出现位置 string& replace(int pos, int n, const string& str); //替换从pos开始n个字符为字符串str string& replace(int pos, int n, const char* s); //替换从pos开始的n个字符为字符串s */ void test() { string s1; string s2(s1); //拷贝构造 string s3("aaa"); //有参构造 string s4(10, 'c');//两个参数 有参构造 cout << s3 << endl; cout << s4 << endl; //赋值 string s5; s5 = s4; //string& assign(const char* s, int n);把字符串s的前n个字符赋给当前的字符串 s5.assign("asgdhagu", 5); cout <<"s5 = " << s5 << endl; //string& assign(const string & s, int start, int n);将s从start开始n个字符赋值给字符串 //从0开始计算 string s6="hfuyeguasdg"; string s7; s7.assign(s6, 2, 4); cout <<"s7 = " << s7 << endl; } void test2() { string s = "hellofhdsu"; //at 和 []区别 []访问越界 直接挂掉 而 at访问越界 会抛出一个 异常 out_of_range try { //s[100]; s.at(100); } catch (out_of_range& e) { cout << e.what() << endl; } } void test3() { string str1 = "ccx"; string str2 = "Wuhan"; str1 += str2; cout << str1 << endl; string str3 = "天安门"; str1.append(str3); cout << str1 << endl; //字符串查找 string str4 = "djshufad"; int pos = str4.find("shu"); //如果找不到子串 返回-1 //int pos = str4.rfind("shu"); //rfind 从右往左查找 cout << "pos = " << pos << endl; //string& replace(int pos, int n, const string & str); //替换从pos开始n个字符为字符串str str4.replace(1, 3, "111"); //d111ufad cout << "str4 = " << str4 << endl; } /* compare函数在>时返回 1,<时返回 -1,==时返回 0。 比较区分大小写,比较时参考字典顺序,排越前面的越小。 大写的A比小写的a小。 int compare(const string& s) const;//与字符串s比较 int compare(const char* s) const;//与字符串s比较 */ void test4() { string str1 = "agyfdgu"; string str2 = "agyfdgu"; if (str1.compare(str2) == 0) { cout << "str1 == str2" << endl; } else if(str1.compare(str2) > 0) { cout << "str1 > str2" << endl; } else { cout << "str1 < str2" << endl; } } //string 子串 //string substr(int pos = 0, int n = npos) const;//返回由pos开始的n个字符组成的字符串 void test5() { string email = "1090645083@qq.com"; int pos = email.find("@"); string userName = email.substr(0, pos); cout << userName << endl; } void test6() { string str = "www.bilibili.com.cn"; //需求:将网址中的每个单词 都截取到vector容器中 vector<string>v; int start = 0; while (true) { int pos = str.find(".", start); if (pos == -1) { string tmp = str.substr(start, str.size() - start); v.push_back(tmp); break; } string tmp = str.substr(start, pos-start); v.push_back(tmp); start = pos + 1; } for (vector<string>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << endl; } } int main() { test6(); system("pause"); return EXIT_SUCCESS; }
3.1 构造、赋值
3.2 字符存取 [ ] , at 区别
3.2.1 at 访问越界抛出out..of.range.异常
3.2.2 [ ]直接挂掉
3.3 拼接、查找、替换
3.3.1 +=append拼接
3.3.2 查找 find 查不到返回-1。 rfind从右往左查
3.3.3 替换 replace
3.4 比较compare
3.4.1 项目= 1 字符串1大 >0 字符串1 小<0
3.5子串
3.5.1 string substr(int pos = 0, int n = npos) const;//返回由pos.开始的n企字符组成的字符串
3.5.2 练习 截取email 中用户名
3.5.3 练习2 将网址中单词截取到vector容器中
3.6 插入insert
3.7 删除erase
3.8 char* 和 string 转换
//char * -> string const char* str = "hello"; string s(str); //string ->char* const char* str2 = s.c_str(); //编译器将const char* 可以隐式类型转换为 string //但不会将string隐式类型转换为const char*
3.8.1 char* 转 string 调用string有参构造string strl("char *")
3.8.2 string 转 char* .c_str() const char*
3.8.3 编译器可以将char隐式类型转换为string,反之不可以。
3.9 小写转大写和大写转小写
3.9.1转大写 touppere
3.9.2转小写 tolower
//小写转大写 str[i] = toupper(str[i]); //大写转小写 str[i] = tolower(str[i]);
4 vector 容器
4.1 与数组类型,但是是动态数组
4.2 动态分配内存并不是原有空间下分配,而是找一个新空间,将原有数据拷贝到新空间下,然后释放掉原有空间。
4.3 构造、赋值
4.4 交换swap
4.5 size 容器的大小
4.6 capacity 容器的容量
4.7 empty 容器是否为空
4.8 resize重新指定容器长度
4.8.1 如果比原来长了,默认用 0 填充,可以用第二个参数代替默认值
4.8.2 如果比原来短了, 超出的部分元素就被删除掉了
4.9 reserve留空间,但是丕初始化
4.10 at、[ ]对元素存取,at越界抛出异常, [ ] 直接挂掉
4.11 第一个元素 front
4.12 最后一个元素 back
4.13 插入 insert(迭代器)
4.14 删除 erase(迭代器)
4.15 清空 clear
4.16 尾插 push_back
4.17 尾删 pop_back
4.18 小练习:
4.18.1 巧用swap来牧缩内存
4.18.2 巧用reserve预留空间
4.19 逆序遍历迭代器reverse_iterator
4.20 如果判断一个容器的迭代器是否支持随机访问
5 deque容器
5.1 双端数组
5.2可以对头部进行插入删除
5.3和vector的测试接口差不多
5.4 不同头部插入 push_front 头部删除 pop_front
1 stack栈容器
1.1 符合先进后出的数据结构
1.2对外接口
1.3构造、赋值
1.4大小size
1.5是否为空empty
1.6栈顶top
1.7 入栈push
1.8出栈popu
1.9 不提供遍历功能,因此没有迭代器
2 queue队列容器
2.1 符合先进先出数据结构
2.2 对外接口
2.3 构造、赋值
2.4 大小size
2.5 是否为空 empty
2.6 队头front
2.7 队尾back
2.8入队push
2.9 出队pop
2.10 不提供遍历功能,没有迭代器
3 list容器
3.1 双向循环链表
3.2 赋值、构造v
3.3 删除remove ( elem) 删除容器中所有与 elem 匹配的元素
3.4 反转 reverse
3.5 排序 sort
3.5.1 黑认从小到大
3.5.2 对于自定义数据类型,必须要指定排序规则
3.5.3 案例给葫芦娃高级排序
set容器
4.1 关联式容器,内部插入数据时候自动排序
4.2 set容器不允许插入重复的key值
4.3 册除 erase ( key)e
4.4 find查找 查找容器中是否有响应的 key的数据, 如果有返回迭代器,没有返回end的位置
4.5 count统计 统计 key的个数,对于set而言,结果要么是0,要么是1
4.6 lower_bound(keyElem): //返回第一个key>=keyElem 元素的迭代器。
4.7 upper_bound(keyElem); //返回第一个key>keyElem元素的迭代器。
4.8 equal..range(keyElem);//返回容器中 key与 keyElem.,相等的上下限的两个迭代器。
4.8.1返回值是一个 对组pair<第一个元素,第二个元素>
4.9 对组声明方式
4.9.1 pair<string, int>p(string("ccx"), 18);
4.9.2 pair<string, int>p2 = make_pair("ccc", 20);
4.9.3 第一个元素 first 第二个元素 second
4.10 set容器只能插入不重复 key 值
4.10.1 insert 返回值是pair< iterator, bool >
4.10.2 bool 代表了插入是否成功
4.11 对于 set 容器的排序
4.11.1 必须要在插入之前指定出排序规则·
4.11.2 配合仿函数实现
4.11.3 自定义数据类型,必须要指定出排序规则
class MyComparePerson { public: bool operator()(const Person& p1, const Person& p2) const { //年龄 升序 return p1.m_Age < p2.m_Age; } };
5 map容器
5.1 关联式容器,key和value每个元素都是对组
5.2插入4种方式·
map<int, int> m; m.insert(pair<int, int>(1, 10)); m.insert(make_pair(2, 30)); m.insert(map<int, int>::value_type(3, 20)); m[4] = 40;
5.3 find(key): //查找键key是否存在,若存在,返回该键的元素的迭代器;/若不存在,返回map.end();
5.4 count(keyElem): //返回容器中key为 keyElem.的对组个数。对map来说,要么是o,要么是1。对multimap_来说,值可能大于1。
5.5 lower_bound(keyElem)://返回第一个key>=keyElem元素的迭代器。
5.6 upper_bound(keyElem)://返回第一个 key-keyElem元素的迭代器。
5.7 equalrange(keyElem):/返回容器中 key与 keyElem ,相等的上下限的两个迭代器。
1 函数对象
1.1 1、函缕对象通常不定义构造函数和析构函数,所以在构造和析构时不会发生任何问题,避免了函数调用的运行时问题。
1.2 2、函崴文对象超出普通函数的概念.,函数对象可以有自己的状态
1.3 3、函泼效对象可内联编译,性能好。用函数指针几乎不可能
1.4 4、模版函数对象使函数对象具有通用性,这也是它的优势之一
1.5 可以作为函数参数传递
2 谓词
2.1 普通函数或者仿函数返回值是bool类型,这样的函数或者仿函威称为谓词
2.2 一元谓词
2.2.1找容器中第一个大于20的数字find_ife
2.3二元谓词
2.3.1 对容器进行排序 sort
3 内建函数对象
- template<class T> T negate<T> //取反仿函数
- template<class T> T plus<T> //加法仿函数
- template<class T> bool greater<T> //大于
头文件 #include<functional>
4适配器
4.1 函数对象适配器
4.1.1 bind2nd或者bind1st将两个参数进行绑定
- bind2nd绑定顺序是一致
- 继承binary.function<类型1,类型2,返回值类型>
- 加const
4.2取反适配器
4.2.1 一元取反not1
- 继承 unary_function<类型1,返回值类型>
- 加const
4.2.2 二元取反 not2
4.3 函数指针适配器
4.3.1 ptr_fun 将函数指针 适配为函数对象
4.4成员函|数适配器
- mem_fun_ref 存放对象本体
- mem.fun 存放对象指针

size_type 由string 类类型和vector类类型定义的类型,用以保存任意string对象或vector对象的长度,标准库类型将size_type定义为unsigned类型。
size_t是为了方便系统之间的移植而定义的。(它就是一个无符号整型,有木有!!!)
在32位系统上定义为 unsigned int 。 在64位系统上定义为 unsigned long
stack 先进后出
queue 先进先出
下面哪些论断是不正确的?为什么?
- (a) 一个类必须至少提供一个构造函数。
- (b) 默认构造函数是参数列表为空的构造函数。
- (c) 如果对于类来说不存在有意义的默认值,则类不应该提供默认构造函数。
- (d) 如果类没有定义默认构造函数,则编译器将为其生成一个并把每个数据成员初始化成相应类型的默认值。
- (a) 不正确。如果我们的类没有显式地定义构造函数,那么编译器就会为我们隐式地定义一个默认构造函数,并称之为合成的默认构造函数。
- (b) 不完全正确。为每个参数都提供了默认值的构造函数也是默认构造函数。
- (c) 不正确。哪怕没有意义的值也需要初始化。
- (d) 不正确。只有当一个类没有定义任何构造函数的时候,编译器才会生成一个默认构造函数。
拷贝、赋值与销毁
1.浅拷贝和深拷贝

浅拷贝是拷贝地址。这样容易造成内存泄漏。

class String { public: String(const char* cstr = 0); String(const String& str); String& operator=(const String& str); ~String(); char* get_c_str() const { return m_data; } private: char* m_data; }; inline String::String(const char* cstr = 0) { if (cstr) { m_data = new char[strlen(cstr) + 1]; strcpy(m_data, cstr); } else { m_data = new char[1]; *m_data = '\0'; } } inline String::~String() { delete[] m_data; } inline String::String(const String& str) { m_data = new char[strlen(str.m_data) + 1]; strcpy(m_data, str.m_data); } inline String& String::operator=(const String& str) { if (this == &str) return *this; delete[] m_data; m_data = new char[strlen(str.m_data) + 1]; strcpy(m_data, str.m_data); return *this; }

是否需要return by reference?
函数的执行的结果如果不放到 local object,就可以传入reference,
堆,栈和内存管理

1. new
Complex* pc = new Complex(1, 2);
(1)分配内存
(2)转型
(3)调用构造函数
2. delete
先调用析构函数,后释放内存



64的16进制是40。是四一。为什么呢?四零借用最后一个最后一位,最后一个零或一来表现我这一块是给出去还是?收回来,那现在我们谈的是给出去了,对操作系统而言是给出去了,对我们的程序而言是获得了。所以你获得的这一定是四一最后一个bit,一定是一。


第一步,找到变量名,如果没有变量名,找到最里面的结构
第二步,向右看,读出你看到的东西,但是不要跳过括号
第三步,再向左看,读出你看到的东西,但是也不要跳过括号
第四步,如果有括号的话,跳出一层括号
第五步,重复上述过程,直到你读出最终的类型。
引用计数:数一数有多少个共享指针指向某个物体。
拷贝构造
#include <iostream> class Date { private: int year, month, day; public: Date(int y=0, int m=0, int d=0) { year = y; month = m; day = d; std::cout << "构造函数" << std::endl; } Date(const Date& t) { year = t.year; month = t.month; day = t.day; std::cout << "拷贝构造函数" << std::endl; } ~Date() { std::cout << "析构函数" << std::endl; } void show() { std::cout << year << " " << month << " " << day << std::endl; } }; int main() { Date d(2023, 5, 6); d.show(); Date d2 ; d2 = d2.show(); return 0; }

初始化成员列表的写法,比普通的函数体内部赋值看起来更加好看,但是这两种写法的效率是一模一样的。读到这里就会有疑问,仅仅便捷到这种程度,没必要弄一个初始化列表出来吧?那我们下面介绍一下,初始化列表比普通函数体内部实现更便捷的应用场合。
(74条消息) C++学习之路-构造函数的初始化列表_c++构造函数初始化列表_Struggle¥的博客-CSDN博客

浙公网安备 33010602011771号