黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难

C++重点笔记
tips
编译和调试的区别
编译(compile):依赖于编译器,将源码转化成目标文件 如.obj
调试:让程序在系统中运行之前查错和改错,找出并修正潜在的错误
-------------------------------------------------------------
B站篇幅有限,没有markdown模式,完整笔记请看https://blog.csdn.net/liushuping528/article/details/126087429?spm=1001.2014.3001.5501
--------------------------------------------------------------
linux c++的四个处理过程
1. 预处理
将所有#include头文件以及宏定义替换成其真正的内容
2. 编译
将经过预处理之后的程序转换成特定汇编代码的过程
3. 汇编
将上一步的汇编代码转换成机器代码,这一步产生的文件叫做目标文件
4. 链接
将多个目标文件以及所需的库文件(.so)链接成最终的可执行文件
数据类型
4种转换较为规范的运算符
`reinterpret_cast<type-name>(expression)`
1. reinterpret_cast 可以将指针类型转换为足以存储指针表示的整型
2. 不能将函数指针转换为数据指针
static_cast
`auto *ptr = static_cast<void(Teacher::*)(QString)>(&Teacher::hungry);`
尖括号里面的转为外面的
用于各种隐式转换,如非const转const,void*转指针,static_cast能用于多态向上转化,如果向下转能成功但是不安全
## 内存模型和名称空间
### 单独编译
将组件函数放在独立的文件中,然后将他们链接成可执行的程序。如若要修改文件,只需重新头文件,然后将它与其他文件编译版本链接,程序的管理更为便捷
**组织策略 可以把程序拆分为三个部分**
1. 头文件:编写函数原型 结构声明
eg:
```cpp
#ifndef STOCK_H_
#define STOCK_H_
struct polar
{
double distance;
double angle;
};
struct rect
{
double x;
double y;
};
polar rect_to_polar(rect xypos);
void show_polar(polar dapos);
#endif
```
2. 源代码文件:函数定义
eg:
```cpp
#include <iostream>
#include<cmath>
#include "stock.h"
polar rect_to_polar(rect xypos) {
using namespace std;
polar answer;
answer.distance = sqrt(xypos.x * xypos.x + xypos.y * xypos.y);
answer.angle = atan2(xypos.x, xypos.y);
return answer;
}
void show_polar(polar dapos) {
using namespace std;
cout << "angle" << dapos.angle << endl;
cout << "distance" << dapos.distance << endl;
}
```
3. 源代码文件:main()和其他使用这些函数的函数放在第三个文件
```cpp
#include <iostream>
#include "stock.h"
using namespace std;
int main() {
rect rplace;
polar pplace;
cout << "enter the x and y values: ";
while (cin >> rplace.x >> rplace.y) {
pplace = rect_to_polar(rplace);
show_polar(pplace);
cout << "next two number(q to quit):";
}
cout << "bye!\n";
return 0;
}
```
> 1.高效的组织策略,编写另一程序,需要使用这些函数时,只需包含头文件,并将函数文件添加到项目列表或make列表中即可。
> 2.这种组织方式也是面向对象(OOP)的体现
**头文件包含的内容:**
* 函数原型
* 结构声明
* 类声明
* 内联函数(~)
* 模板声明(~)
头文件编写
```cpp
#ifndef HEAD_H_ //根据include名选择名称,并加上下划线 以防止被其他地方定义
#define HEAD_H_
…… //file content
#endif
```
名称空间 -ing
名称可以是函数、变量、结构、类以及类的成员,随着项目的增大,名称相互冲突的可能性也将增加
```cpp
namespace jack{
double pail;
void fetch();
int pal;
struct well{}
}
```
名称空间是开放的(open),
1. .
```cpp
namespace jack{
char name[10];
}
```
2. jack名称空间为fetch()函数提供原型。可以在文件后面再次使用Jack名称空间定义这个函数
```cpp
namespace jack{
void fetch(){
……
}
}
```
using声明和using编译
* **using声明**由using和被限定的名称组成
*
`using jack::pail; // 一个using声明`
using声明将特定的名称添加到它所属的声明区域中。
```cpp
int main(){
using jack::pail;
cin >> pail;
cin >> ::pail; //读入进去一个pail全局变量
}
```
using声明使一个名称可用,而using编译指令使所有名称都可用。
* **using编译指令**由using namespace和名称空间组成
*
`using namespace jack; //一个using编译声明`
using namespace
**总结:** 导入名称时,首选使用作用域解析运算符或using声明的方法。
### const成员函数
const关键字放在函数的括号后面 stock类中的函数声明
`void show() const //函数声明`
函数定义的开头
`void stock :: show() const //函数定义 不被改变`
只要类不修改调用对象,就应将其声明为const
## effective C++
### 永远在使用对象之前先将它初始化
构造函数做好使用成员初始列,而不要在构造本体函数内使用赋值操作
### 8-2法则
一个程序的80%的资源用于20%的代码身上,80%执行时间花在20%的代码身上,80%执行时间花在20%的代码
8-2 法则告诉我们,如果东一块西一块的改善程序,病急乱投医,头痛医头脚痛医脚,不会有太大帮助。
枚举类型 enum
定义:
enum 类型名{枚举值表}
枚举值表也叫枚举元素列表,列出定义的枚举类型的所有可用值,各个值之间用“,”分开。
`enum Suit{Diamonds,Hearts,Clubs};`
数组
c++的运算符优先要求使用括号
数组类比于指针
数组名是第一个元素的地址
int* a的类型是int* int a的类型是int
前者a是指向整型数据的指针,它的本质是一个地址,后者就是一个数据了
`Dog* dog = new Dog;` 分配一片内存 并把内存的地址赋给 dog
### 无符号类型
不能存储负数值的无符号变体,其优点是可以增大变量能够存储的最大值
(short范围-32768-32767,unsign short范围0-65535)
### 指针表示动态数组
使用new[]运算符创建数组时,将采用动态联遍,即将在运行时为数组分配空间,其长度也将在运行时设置
使用完成后,应用delete[]释放占用的内存
```cpp
int size;
cin >>size;
int *arr = new int [size];
……
delete[] arr;
```
### 数组区间的函数
指定元素区间 可以通过传递两个指针完成:一个指针标识数组的开头,另一个指针标识数组的尾部
```c++
int sum_arr(const int *begin,const int *end); //函数声明
int main(){
int arr[10];
int sum = sum_arr(arr,arr+3); //定义区间
}
```
### const保护数组
为防止函数无意中修改数组的内容,可在声明形参时使用关键字**const**
`void show_array(const double ar[],int n)`
### 结构体
```c++
struct typename {
char name[10];
int ages;
}
```
1. new 创建动态结构
`inflat *ps = new inlat;`
使用完成后要用**delete**释放内存 `delete ps;`
> ps -> price 也是指向结构的price成员
> ps 是指向结构的指针,则*ps就是指向的值--结构本身
> (*ps)是一种结构 (*ps).name 是该结构的name成员
## 函数
### 函数原型
原型可以帮助编译器完成许多工作,降低程序出错的机率,具体有一下几点:
1. 编译器正确处理函数返回值
2. 编译器检查使用的参数数目是否正确
3. 编译器检查使用参数类型是否正确
### #define的定义
(文本替换)
define 是宏定义,程序在预处理阶段将用define定义的内容进行替换。程序在运行时,常量表中并没有用define定义的常量,系统不会为它分配内存
1. #define定义一个标识符来表示一个常量。
特点:定义的标识符不占内存,只是一个临时的符号,预编译后这个符号就不存在了
* 宏展开是在预处理阶段完成的,这个阶段把替换文本只看做一个字符串,并不会有任何的计算发生。
* 宏其实就是简单的文本替换。
定义标识符的一般形式:
```c++
#define 标识符 常量 //注意,最后没有分量
```
2. 在大规模的开发过程中,特别是跨平台和系统的软件里,define最重要的的功能就是条件编译
```cpp
#ifdef WINDOWS
……
#endif
#ifdef LINUX
……
#endif
```
可以在编译的时候通过#define设置编译环境。
### 函数指针
使用函数指针的三个步骤:
1. 声明函数指针
2. 让函数指针指向函数地址
3. 通过函数指针调用函数
**声明函数指针**
```cpp
double pam(int) //函数原型
double (*f)(int) //函数指针
```
pam 替换为了(*f).由于pam是函数名 即(*f)也是函数名, f 是函数指针
**函数指针调用函数**
```cpp
//函数指针调用函数
f = pam;
double y = pam(2);
double x = (*f)(5);
```
```cpp
#include<iostream>
using namespace std;
double jack(int);
double rose(int);
void accrate(int lines, double(*pf)(int)); //声明指针函数
int main() {
int code;
cin >> code;
cout<<"jack和rose输入"<<code<<"行代码算花费的时间"<<endl;
accrate(code, jack);
accrate(code, rose);
}
double jack(int n) {
double sum = n * 0.5;
return sum;
}
double rose(int n) {
double sum = n * 0.7;
return sum;
}
void accrate(int lines, double(*pf)(int)) {
//double um = (*pf)(lines);
cout << lines << " lines may have " << (*pf)(lines) << "mins" << endl;
}
```
### 回调函数
> 回调函数就是一个通过函数指针调用的函数,如果你把函数的指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说回调函数
把一段可执行的代码像参数传递那样传给其他代码,而这个代码在某个时刻被调用执行,叫做回调。
如果代码立即被执行就称为**同步回调**,如果在之后晚点的某个时间在执行,则称为**异步回调**
回调函数的作用 ,**解耦**
```cpp
#include "../apue.h"
int Call_back_1(int x)
{
printf("Hello,this is Call_back_1,the value is %d\n",x);
return 0;
}
int Call_back_2(int x){
printf("Hello,this is Call_back_2,the value is %d\n",x);
return 0;
}
int Call_back_3(int x){
printf("Hello,this is Call_back_3,the value is %d\n",x);
return 0;
}
int Handle(int x,int (*Callback)(int x))
{
printf("Entering Handle Function.\n");
Callback(x);
printf("Leaving Handle Function.\n");
return 0;
}
int main(){
int x = 1;
int y = 2;
int z = 3;
printf("Entering Main Function.\n");
Handle(x,Call_back_1);
Handle(y,Call_back_2);
Handle(z,Call_back_3);
printf("Leaving Main Function.\n");
return 0;
}
```
### 引用&
1. 形参需要修改实参时
2. 当实参较大时,如(数据 结构体)传递引用 系统不会生成临时变量 较小的内存消耗
### 形参实参的三种传值方式
1. 按值传递
2. 按地址传递 形参改变实参
3. 按引用传递
```cpp
#include<iostream>
void swap1(int a, int b) {
int temp;
temp = a;
a = b;
b = temp;
}
void swap2(int* p, int* q) {
int temp;
temp = *p;
*p = *q;
*q = temp;
}
void swap3(int& a, int& b) {
int temp;
temp = a;
a = b;
b = temp;
}
int main() {
int i = 10;
int j = 20;
//按值传递
swap1(i, j); //形参不会对实参进行修改
std::cout << "i的值是" << i << std::endl;
std::cout << "j的值是" << j << std::endl;
//按地址传递
swap2(&i, &j); //形参会对实参进行修改
std::cout << "i的值是" << i << std::endl;
std::cout << "j的值是" << j << std::endl;
//按引用传递
swap3(i, j); //形参不会对实参进行修改
std::cout << "i的值是" << i << std::endl;
std::cout << "j的值是" << j << std::endl;
return 0;
}
```
## STL模板
### vector容器
与数据相似,也成为单端数组(类比于栈)
**vector与普通的数组的区别**
数组时静态空间,而vector可以动态扩展
**动态扩展**
并不是在原有的基础上进行拓展,而是找一个更大的空间,将原数据拷贝到新空间,释放原空间
vector迭代器 支持随机访问的迭代器
vector四种构造方法
1. 默认构造
2. 区间构造
3. 拷贝构造
4. n个元素构造
```cpp
#include<iostream>
#include<vector>
using namespace std;
void printvector(vector<int> &v) {
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
void test() {
//默认构造
vector<int>v1;
for (int i = 0; i < 50; i++) {
v1.push_back(i);
}
printvector(v1);
//通过区间的方式进行构造
vector<int>v2(v1.begin(), v1.end());
printvector(v2);
//N个elem方式构造
vector<int>v3(10, 123);//(个数,元素)
printvector(v3);
//拷贝构造
vector<int>v4(v3);
printvector(v4);
```
**vector的增删查**
```cpp
struct review
{
string title;
int rating;
};
bool Fillreview(review& re);
void printbook(vector<review>& b1);
int main() {
vector<review>book;
review temp;
while (Fillreview( temp)) {
book.push_back(temp);
}
printbook(book);
vector<review>oldbook(book);
printbook(oldbook);
book.erase(book.begin() + 1, book.begin() + 3); //vector的删除
printbook(book);
book.insert(book.begin(), oldbook.begin() + 1, oldbook.begin() + 2);//vector的插入
printbook(book);
book.swap(oldbook);
printbook(book);
return 0;
system("pause");
}
bool Fillreview(review& re)
{
cout << "please enter a book name" << endl;
getline(cin,re.title);
if (re.title == "quit")
{
return false;
}
else
cin >> re.rating;
if (!cin)
return false;
while (cin.get()!='\n')
{
continue;
}
return true;
}
void printbook(vector<review>& b1)
{
for (vector<review>::iterator it =b1.begin();it!=b1.end();it++)
{
cout << (*it).rating <<" "<<(*it).title<< endl;
}
cout << "--------------------" << endl;
}
```
**支持随机访问的容器 都可以用标准算法sort进行排序**
### list双向循环链表
由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器
list 不支持随机存取
List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的
STL中**list和vector是两个最常使用的容器**,各有缺点
`list.reverse();` 双向链表的反转
`list.sort()` 双向链表的排序
`font` 首元素
`back` 尾部元素
**list自定义类型排序**
> 自定义类型 类类型or结构类型 要先指定类型规则
**语法**
```cpp
//指定排序规则 如果年龄相等 按照体重排序
bool compareage(Person& p1, Person& p2) {
if (p1.m_age == p2.m_age) {
return p1.m_weight < p2.m_weight;
}
else
{
return p1.m_age < p2.m_age;
}
}
```
```cpp
#include<iostream>
#include<list>
#include<string>
using namespace std;
class Person {
public:
Person(string name, int age,float weight) {
this->m_name = name;
this->m_age = age;
this->m_weight = weight;
}
string m_name;
int m_age;
float m_weight;
};
void printlist(list<Person>& p) {
for (list<Person>::const_iterator it=p.begin();it!=p.end();it++)
{
cout << (*it).m_name << it->m_age <<" " << it->m_weight << " ";
}
cout << endl;
}
//指定排序规则 如果年龄相等 按照体重排序
bool compareage(Person& p1, Person& p2) {
if (p1.m_age == p2.m_age) {
return p1.m_weight < p2.m_weight;
}
else
{
return p1.m_age < p2.m_age;
}
}
void test() {
Person p1("zhangsan", 56,50.2);
Person p2("lisi", 16,53.2);
Person p3("wangwu", 16,59.9);
Person p4("zhaoliu", 17,45.9);
Person p5("gouba", 19,49.3);
list<Person>ll;
ll.push_back(p1);
ll.push_back(p2);
ll.push_back(p3);
ll.push_back(p4);
ll.push_back(p5);
printlist(ll);
ll.sort(compareage);
printlist(ll);
}
int main() {
test();
return 0;
}
```
### 关联容器set,mutiset
所以的元素都会在插入时自动排序
set与multiset属于关联容器,底层结构是二叉树实现
**区别**
1. set不允许容器有重复的元素
2. multiset允许容器有重复的元素
set的插入使用的是insert,没有push_back这个方法
empty();
size();
swap();
**插入删除**
insert()
erase(迭代器) erase(容器元素)
clear(); 清空
**查找和统计**
find(); //查找key是否存在,返回该元素的迭代器不存在 返回set.end()
cout(); 要么是0 要么是1
### set容器排序规则
set的默认排序规则是从小到大,改变排序规则从大到小
利用仿函数,改变排序规则
### map/multimap容器
map所有元素都是一对
第一个key值(索引的作用)第二个valu值(实值)
所有元素都会根据元素的键值自动排序
关联容器,二叉树实现
可以通过key值快速的找到value值
map与multimap 的区别 是能不能插入重复的key值
```cpp
void printmap(map<int, int>& ma) {
for (map<int,int>::const_iterator it =ma.begin();it!=ma.end();it++)
{
cout << "key= " << (*it).first << " value=" << (*it).second << endl;
}
cout << endl;
}
void test() {
//map容器
map<int, int>m; //k v 创建map容器
m.insert(pair<int, int>(1, 10));
m.insert(pair<int, int>(2, 28));
m.insert(pair<int, int>(4, 20));
m.insert(pair<int, int>(6, 21));
m.insert(pair<int, int>(9, 320));
m.insert(make_pair(10, 22)); //make_pair 插入
printmap(m);
map<int, int>m2(m); //拷贝构造函数
printmap(m2);
map<int, int>m3;
m3 = m2; //赋值
printmap(m3);
cout << m[6] << endl; //可以使用[]查找;
cout<<m.size()<<endl;
//删除
m.erase(m.begin()); //按照迭代器删除
m.erase(6); //按照key值删除
printmap(m);
}
```
查找 统计
`find()` 查找key值是否存在,不存在返回set.end;
```cpp
map<int, int>::iterator it = m.find(9);
cout << (*it).first <<it->second<<endl;
```
cout() 0or1
使用仿函数改变map的默认排序 由大到小
> 仿函数 传入的时候传入一个数据类型,在类中重载了()函数
重载()
```cpp
class Mycompare {
public:
bool operator()(int v1, int v2) const 自定义排序规则
{
return v1 > v2;
}
};
```
```cpp
void printmap(map<int, int,Mycompare>& ma) {
for (map<int, int>::const_iterator it = ma.begin(); it != ma.end(); it++)
{
cout << "key= " << (*it).first << " value=" << (*it).second << endl;
}
cout << endl;
}
void test() {
//map容器
map<int, int,Mycompare>m; //k v 创建map容器
m.insert(pair<int, int>(1, 10));
m.insert(pair<int, int>(2, 28));
m.insert(pair<int, int>(4, 20));
m.insert(pair<int, int>(6, 21));
m.insert(pair<int, int>(9, 320));
m.insert(make_pair(10, 22));
printmap(m);
}
```
### 函数对象
重载函数调用操作符的类,其对象成为函数对象
对()重载的函数 也叫仿函数
```cpp
#include<iostream>
using namespace std;
//1. 函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
class Mycompare {
public:
int operator()(int v1, int v2) {
return v1 + v1;
}
};
void test() {
Mycompare myadd;
cout << myadd(10, 10) << endl;
}
//2. 函数对象可以有自己的状态
class Myprint {
public:
Myprint() {
count = 0;
}
void operator()(string test) {
cout << test << endl;
this->count++;
}
int count; //内部自己的状态
};
void test1() {
Myprint myprint;
myprint("helloworld");
myprint("helloworld");
myprint("helloworld");
cout << "myprint调用次数" << myprint.count << endl;
}
void doprint(Myprint& mp, string test) {
mp(test);
}
//3. 函数对象可以作为参数传递
void test2() {
Myprint my1;
doprint(my1, "hello C++");
}
```
#### 内建仿函数
**算术仿函数**
```cpp
#include<iostream>
#include<functional> //使用内建函数对象时,需要引入头文件 #include<functional>
using namespace std;
void test() {
//negate 一元仿函数 取反函数
negate<int>n;
cout << n(50) << endl;
}
void test1() {
//plus 二元仿函数 加法
plus<int>p;
cout << p(10, 20) << endl;
}
```
**关系仿函数**
自定义降序仿函数定价与`greater<int>()` -> 需要使用`#include<functional>`
自定义
```cpp
class Mycompare {
public:
bool operator()(int v1, int v2) const 自定义排序规则
{
return v1 > v2;
}
};
```
### STL-常用算法
算法主要是由头文件<algorithm> 最大的一个,涉及比较、交换、查找、遍历<functional>仿函数 <numeric>
**常用遍历算法**
`for_each(iterator_beg;iterator_end;函数 仿函数);`
```cpp
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//仿函数
class Myprint {
public:
void operator()(int val) {
cout << val;
}
};
void printv(int val) {
cout << val<<endl;
}
void test() {
vector<int>v1;
for (int i=0;i<10;i++)
{
v1.push_back(i);
}
//for_each算法
for_each(v1.begin(),v1.end(), printv);
for_each(v1.begin(), v1.end(), Myprint());
}
```
`transform(v.begin(),v.end(),target.begin(),fun_函数)`
> 搬运的目标容器必须提前开辟空间,否则无法正常搬运
```cpp
class Transform {
public:
int operator()(int v) {
return v;
}
};
class Print {
public:
void operator()(int val) {
cout << val << endl;
}
};
void test() {
vector<int>v1;
for (int i=1;i<10;i++)
{
v1.push_back(i);
}
vector<int>target;
target.resize(v1.size()); //目标容器需要提前开辟空间
transform(v1.begin(), v1.end(), target.begin(), Transform());
for_each(target.begin(), target.end(), Print());
}
```
**常用的查找算法**
1. `find`
`find(iterator.beg(),iter.end(),value)`
find 可以在容器中找指定的元素,返回值是迭代器
自定义类型 需要写仿函数 对"=="就行重载
```cpp
class Person {
public:
Person(string name, int age) {
this->m_name = name;
this->m_age = age;
}
string m_name;
int m_age;
bool operator ==(Person p2 ) {
if (this->m_age==p2.m_age&&this->m_name==p2.m_name)
{
return true;
}
}
};
```
2. `find_if`
按条件查找
` vector<Person>::iterator it =find_if(p.begin(), p.end(), fun_仿函数);`
3. `binary_search`
二分查找 对于有序的数列
`bool binary_search(it.beg(),it.end(),查找值)`
```cpp
bool ret = binary_search(v1.begin(), v1.end(), 8);
cout << ret << endl;
```
4. `count`
统计自定义类型时 需要配合重载`operator==`
自定义数据类型 就在自定义的类型里面就行仿函数的定义
内建型 就添加写一个类对运算符进行重载
5. `count_if`
```cpp
class mix14 //谓词
{
public:
bool operator()(int v1) {
return v1 > 12;
}
};
int num =count_if(v1.begin(), v1.end(), mix14());
```
**常用排序算法**
1. `sort(it.beg(),it.end(),谓词)`
```cpp
class ree {
public:
bool operator()(int v1, int v2) {
return v1 > v2;
}
};
sort(v1.begin(), v1.end(), ree());
```
2. 洗牌算法
random_shuffle(it.beg(),it.end())
```cpp
srand((unsigned int)time(NULL)); //实时时间
vector<int>v1;
for (int i=1;i<10;i++)
{
v1.push_back(i);
}
random_shuffle(v1.begin(), v1.end());
for_each(v1.begin(), v1.end(), print);
```
3. **merge算法**
```cpp
void test() {
vector<int>v1;
vector<int>v2;
for (int i=1;i<10;i++)
{
v1.push_back(i);
v2.push_back(i + 2);
}
vector<int>target;
target.reserve()
target.resize(v1.size() + v2.size()); //目标容器定义大小
merge(v1.begin(), v1.end(), v2.begin(), v2.end(), target.begin());
for_each(target.begin(), target.end(), myprint);
}
```
4. **reverse算法**
` reverse(target.begin(), target.end());` 容器元素反转
常用的拷贝和替换算法
`copy` 容器内指定范围的元素拷贝到另一容器总·
```cpp
vector<int>tar2;
tar2.resize(6);
copy(target.begin(), target.begin() + 6, tar2.begin());
```
`replace`
`replace(tar3.begin(), tar3.end(), 5, 500); ` //将区间的5 替换成500
`replace_if`
```cpp
//谓词
class mix5 {
public:
bool operator()(int val) {
return val > 5;
}
};
replace_if(tar3.begin(), tar3.end(), mix5(), 3000);
```
两个容器元素合并,并储存在另一个容器