- 存放在全局区,在程序结束后释放
namespace Han{
int a = 0;
}
Han::a = 1;
using Han::a;
a = 1;
-
using namespace Han;
-
::
成为域解析操作符号,用来指明要使用的命名空间。
代码中的 string
、cin
、cout
都位于命名空间 std
。
- 不同命名空间内的名字可以重复,从而避免命名冲突问题
- 推荐在函数内部声明 std
总结常用的几种输入输出形式。
从缓冲流中读入数据
以空格、制表符、回车结束
读取每个字符,可以以EOF结束
cout
是 C++ 中 ostream
类型的对象,该类被封装在 <iostream>
库中。ostream
对象只能有一个。
clear()
清空缓冲区,cin
同样适用
i 和 ++i 会在栈里面保存 i 的引用,而 i++ 会在栈里面保存数字。
cout << ++i << i++ << i << i++ << ++i << endl;
-
将 endl 压入栈中, i 值不变;
-
将 i 的引用压入栈中, i 的值加 1 变成 2( ++i );
-
将 2 压入栈中, i 的值加 1 变成 3( i++ );
-
将 i 的引用压入栈中, i 的值不变( i );
-
将 3 压入栈中, i 的值加 1 变成 4( i++ );
-
将 i 的引用压入栈中, i 的值加 1 变成 5( ++i );
-
将栈里的数据依次弹出,即可得到 53525 。
( 因为i的值是 5 ,所以所有 i 的引用都是 5 )
- 默认以十进制输出。
setbase
和setiosflags
包含在<iomanip>
中
cout << i << endl;
cout << dec << i << endl; //十进制
cout << oct << i << endl; //八进制
cout << hex << i << endl; //十六进制
cout << setiosflags(ios::uppercase); //字母大写输出
cout << setbase(8) << i << endl; //以n进制输出,n只能取 8,10,16
-
头文件:
<iomanip>
-
均采用**“四舍五入”**控制精度
-
默认浮点数输出有效位数是 6 位(若前面整数位数大于 6 位,使用科学计数法输出)
cout << setprecision(3) << i << endl; //输出n位有效数
cout << fixed << setprecision(3) << i << endl; //输出小数后n位
cout << setiosflags(ios::fixed); //控制小数点后精度
- 头文件:
<iomanip>
cout << setiosflags(ios::showpoint); //输出小数点
cout << setiosflags(ios::showpos); //输出正负号
- 头文件:
<iomanip>
cout << setfill('*') << setw(8) << 1.22 << endl;
//output: ****1.22
地址:https://blog.csdn.net/qq_35481167/article/details/82792103
使用:cast-name<target_type>(expression);
相较于 C 的类型转换,C++ 可以进行错误检查。如果 target_type
是引用类型,则转换结果是左值。
任何具有明确定义的类型转换,只要不包含底层const,都可以使用。当需要把一个较大的算术类型赋值给较小的类型时,static_cast
非常有用。还可以将 void*
转换为初始的指针类型,此时应确保指针的值不变。
改变运算对象的底层 const
,一般用于改变指针或引用的 const
属性,从而间接修改指向的值。而变量本身的 const
属性是不能去除的,会产生未定义的结果。
用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果时非法的对于指针返回null,对于引用抛出异常。 **向上转换:**指的是子类向基类的转换。 **向下转换:**指的是基类向子类的转换。
几乎什么都可以转,可能会出问题,尽量少用。
type (expr);
(type) expr;
如果旧式强制类型换成 const_cast
和 static_cast
也同样 合法,则其转换执行如同 const_cast
和 static_cast
;若不合法,则其转换执行如同 reinterpret_cast
。
常量必须初始化。所谓指向常量的指针和引用,不过是它们自己觉得自己指向了常量,而可以通过直接修改指向的值来修改,初始化时没有要求一定要指向一个常量。
从右向左看,与标识符最近的符号,对变量的性质影响最大。顶层const 表示指针本身是否为常量,底层const 表示指针指向的值是否为常量。赋值时,需要考虑左右值是否都具有底层const,或者右值是否可以转换成const。
const int *p;
int const *p;
- 本质是指针,指向了一个常量
- 可以更改指向,可以修改
p
- 不可以更改指向地址存放的值,不可以修改
*p
- 可以存 非常量 的地址
int *const p;
- 本质是常量,所以必须 初始化
- 不可以更改指向,不可以修改
p
- 可以更改指向地址存放的值,可以修改
*p
const int* const p;
- 不可以更改指向
- 不可以更改指向地址存放的值
const int& ref = 10
-
用来修饰形参,防止误操作
-
编译器将代码修改为:
int temp = 10; const int& ref = temp;
-
ref 只读,不可修改
-
初始化时,对引用的对象是否为常量不作要求,所以,此时可以更改引用对象的值。
声明为 constexpr
的变量一定是一个常量,而且必须用常量表达式初始化。
constexpr
只对指针本身有效,而与指针所指的对象无关。以下代码中,p是一个指针常量,而q是一个常量指针。
const int *p = nullptr;
constexpr int *q = nullptr;
- 存放CPU执行的机器指令
- 共享,只读
- 存放全局变量和静态变量
- 还包含了常量区
- 数据在程序结束后由OS释放
- 存放局部变量、函数的参数值等
- 由编译器自动分配释放
- 利用
new
在堆区开辟内存 - 利用
delete
手动释放内存 - 由程序员分配释放,若程序员不释放,在程序结束时由操作系统回收
-
引用是数据的别名
-
引用在定义时必须初始化
-
一旦初始化后,不能再引用其它数据
-
引用作函数的返回值
- 不要做局部变量的引用
int& test(){ int a = 10; return a; } int main(){ int &ref = test(); cout << ref << endl; //true,10; 编译器保留 cout << ref << endl; //false; 内存释放 return 0; }
-
函数的调用作为左值,必须返回引用
int& test (){ static int a = 20; return a; } int main () { int& ref = test (); cout << &ref << endl; //003DA000 cout << ref << endl; //20 test () = 1000; cout << &test () << endl;//003DA000 cout << ref << endl; //1000 return 0; }
-
本质是指针常量
//编译器发现引用,自动转换为 int* const ref = &a; void func (int& ref){ ref = 100; } int main (){ int a = 10; //编译器发现引用,自动转换为 int* const ref = &a; int& ref = a; //ref是引用,自动转换为 *ref = 20; ref = 20; cout << a << endl; //20 cout << ref << endl; //20 func(a); cout << a << endl; //100 cout << ref << endl; //100 return 0; }
-
常量引用
const int& ref = 10
-
用来修饰形参,防止误操作
-
编译器将代码修改为:
int temp = 10; const int& ref = temp;
-
ref 只读,不可修改
-
初始化时,对引用的对象是否为常量不作要求,所以,此时可以更改引用对象的值。
-
- 可以不传递有默认值的参数
int func (int a, int b = 20, int c = 30){
return a + b + c;
}
int main (){
cout << func(10) << endl; //60
}
-
传递有默认值的参数时,覆盖默认值
func(10,30) == 70
-
有默认值的形参,必须在形参表中靠右
-
函数声明和函数实现,只能一个有默认参数
仅提供数据类型,可以有默认参数
void func (int a, int = 10){
cout << “this is func" << endl;
}
int main (){
func (10,10);
return 0;
}
满足条件:
- 同一个作用域下
- 函数名称相同
- 返回类型相同
- 函数参数类型不同,或个数不同,或顺序不同
void func (){
cout << "func()" << endl;
}
void func (int a){
cout << "func(int a)" << endl;
}
void func (double a){
cout << "func(double a)" << endl;
}
int main (){
func();
func(10);
func(3.14);
return 0;
}
void func (int& a){
cout << "int& a" << endl;
}
void func (const int& a){
cout << "const int& a" << endl;
}
int main (){
int a = 10;
func(a); //调用 func1
func(10); //调用 func2
return 0;
}
-
因为 a 是变量,10是常量
-
因为
int& a = 10
不合法,而const int& a = 10
合法 -
由于函数在进行值传递时,需要重新分配内存并拷贝值,而当参数是复杂的结构体时,值传递就大大的浪费了空间。所以,可以使用传递
const
和引用,既保证了值不被修改,又保证了不浪费空间。
void func (int a, int b = 10){
cout << "func(int a, int b = 10)" << endl;
}
void func (int a){
cout << "func(int a)" << endl;
}
int main(){
func(10); //出错,二义性,func1,func2均可调用
return 0;
}
- 举例
class Circle{
public: //访问权限
int r;
double calCircumstance(){
return 2 * r * 3.14;
}
};
int main (){
Circle c1; //类的实例化
c1.r = 10;
cout << "周长为 " << c1.calCircumstance() <<endl;
return 0;
}
-
注意:
- 类中若不声明访问权限,默认为
private
- 类中的属性和行为,统一成为成员
- 访问权限冒号以下,皆为权限范围内成员
- 类中若不声明访问权限,默认为
class
默认权限是私有private
struct
默认权限是公共public
class 的成员默认权限是 private
访问权限 | 类外可否访问 | 子类可否访问父类 |
---|---|---|
public | 可以 | 可以 |
private | 不可以 | 不可以 |
protected | 不可以 | 可以 |
优点:
-
可以自己控制读写权限
-
对于写,可以检测数据的有效性
-
通过
setAttribution
方法写入,控制数据范围 -
而不是通过
class.attribution
直接写入class people{ private: int age; public: void setAge (int a){ if ( a>=0&&a<=150 ) //控制数据范围 age = a; else cout << "写入失败" << endl; } } int main (){ people stu; stu.age = 150; //无法访问 stu.setAge(150); //写入失败 return 0; }
-
全局函数、类,成员函数可以作为友元,可以访问类中的私有变量,写在类中
class building{
//全局函数
friend void func(building* b);
//类
friend class goodFriend;
//成员函数
friend void goodfd::vst();
public:
building(){
stRoom = "客厅";
bdRoom = "卧室";
}
private:
string stRoom;
string beRoom;
};
void func(buildind* b){
cout << b->bdRoom << endl;
}
class goodFriend{
public:
goodFriend(){
b = new building;
}
void visit();
};
//类外定义成员函数,构造函数也可以
void goodFriend::visit(){
cout << b->stRoom << endl;
}
class goodfd{
public:
goodfd(){};
void vst(){
b = new building;
cout << b->stRoom << endl;
}
};
类名 ( ) {}
- 函数名称与类名相同
- 可以有参数,因此可以重载
- 在对象实例化时,自动调用构造函数,且只会调用一次
- 无参构造函数也叫默认构造函数
~类名 ( ) {}
- 没有返回值,不写
void
- 函数名称与类名相同,在名称前加上
~
- 不可以有参数,因此不可以重载
- 在对象销毁前会自动调用析构函数,且只会调用一次
- 类中必须有构造函数和析构函数
- 若没有,编译器会自动补上构造和析构的空语句函数
Person (const Person& p) {
cout << "copy" << endl;
age = p.age;
}
- 以值传递方式作为函数形参时,会调用拷贝构造函数
- 当前类作为函数返回值时,会调用拷贝构造函数
-
括号法
Person p1; //无参 Person p2(10); //有参 Person p3(p2); //拷贝
-
调用默认构造函数时,不要使用 ( )
如:
Person p1 ();
因为编译器会认为这是函数声明
-
-
显示法
Person p1; Person p2 = Person(10); //等号右侧称为匿名对象 Person p3 = Person(p2);
-
当前行执行结束后,系统立即回收匿名对象
-
不要利用拷贝构造函数,初始化匿名对象
如:
Person (p3)
因为编译器会认为
Person (p3) == Person p3
-
-
隐式转换法
Person p4 = 10; //相当于Person p4 = Person(10) Person p5 = p4;
默认情况下,c++编译器至少给一个类添加三个函数:
- 默认构造函数(无参)
- 默认析构函数(无参)
- 默认拷贝函数(拷贝属性值)
调用规则:
- 若定义了有参构造,则C++不再提供默认无参构造,但会提供默认拷贝构造
- 若定义了拷贝构造,则C++不再提供其他构造函数
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新开辟空间,再进行赋值操作
- 编译器的拷贝构造函数,都是浅拷贝
- So,如果类中属性有在堆区开辟的,一定要以自提供拷贝构造函数,防止浅拷贝带来的问题
用于初始化类中的属性
class person {
public:
int m_A;
int m_B;
person (int a, int b): m_A (a), m_B (b) {}
};
int main (){
person p(10,20);
}
- 初始化列表中,属性值也可为常量
class person {
public:
int m_A;
int m_B;
person(): m_A (10), m_B (20) {}
};
int main (){
person p;
}
-
先运行类成员的构造函数,再运行自己的构造函数
-
多个类成员,按代码行顺序运行
-
因为类的对象为本地变量,所以存放再栈区,先进后出运行析构函数
-
所有对象共享一份数据
-
在编译阶段分配内存
-
类内声明,类外必须初始化
int Person::m_A = 10
-
静态成员函数只能访问静态成员变量
-
同样有访问权限
-
通过类名访问
Person::m_A = 100;
Person::func();
-
通过对象访问
Person p; p.func(); cout << p.m_A << endl;
-
空对象占用1个字节,是为了区分每一个空对象在内存中的位置
-
类的大小为 类中非静态成员变量大小的总和
-
指向被调用的成员函数所属的对象
-
不需要定义,直接使用
-
本质是指针常量,指向不可以修改
-
当形参和成员变量同名时,可用
this
指针来区分class Person{ public: int age; //命名规范:int m_Age; Person (int age){ this->age = age; } }
-
返回对象本身,用
return *this
//返回对象本身,要引用 //若以值返回,则会创建新对象 Person& addAge (Person& p){ this.age += p.age; return *this; } int main (){ Person p1(10); Person p2(10); //链式编程思想 p2.addAge(p1).addAge(p1).addAge(10); return 0; }
-
空指针也可以访问成员函数
-
该成员函数中不可以有成员变量
class Person{ public: int age; void showMsg() { cout << "Message" << endl; } void showAge() { if ( this==NULL ) //保证代码健壮性 return; cout << this->age << endl; } } int main() { Person* p = NULL; p->showMsg(); //ok p->showAge(); //wrong return 0; }
- 也称 常量成员函数
- 定义:成员函数后加
const
的函数 - 常函数内不可以修改成员属性
- 成员属性声明时加关键字
mutable
后,则可以在常函数中修改 - 隐式地将
this
指针转变为 指向常量的指针,即 指针常量
class person{
public:
int m_A;
mutable int m_B;
void showPerson() const{
this->m_B = 100;
//this->m_A = 100;
}
}
-
定义:声明对象前加
const
的对象A const a(3, 4);
-
常对象只能调用常函数
-
在生存期内不能被更新,但必须被初始化
通过 operator+(-/=/<</++/==/!=/())
重载运算符
class person{
friend ostream& operator<< (ostream &cout, person &p);
private:
int m_A;
int m_B;
public:
person () {}
person (int a, int b){
this->m_A = a;
this->m_B = b;
}
person operator+ (person &p){
person tmp;
tmp.m_A = this->m_A + p.m_A;
tmp.m_B = this->m_B + p.m_B;
return tmp;
}
};
//重载左移要作为全局函数
ostream& operator<< (ostream &cout, person &p){
cout << p.m_A << " " << p.m_B;
}
int main(){
person p1(1,2);
person p2(1,2);
person p3 = p1 + p2;
cout << p3.m_A << " " << p3.m_B << endl;
cout << p << endl;
return 0;
}
注意:
- 不能重载内置类型的运算符,如
int
等 - 不要滥用运算符重载
- 单目运算符最好重载为成员函数,双目最好为友元函数。
- =、[] 只能重载为成员函数,<< 和 >> 只能重载为友元函数。
子类(派生类)继承父类(基类)
class father{
public:
int a;
};
class son: public father{
public:
int b;
};
继承方式 | 子类 |
---|---|
公共继承 | 私有不可访问,其他权限不变 |
保护继承 | 公共和保护成员变为保护权限,私有不可访问 |
私有继承 | 公共和保护成员变为私有,私有成员不可访问 |
子类会继承父类的所有非静态成员属性,不论权限。所以,子类的内存占用大小为 父类的所有非静态成员属性 + 子类本身的非静态成员属性。而父类中的私有成员属性,被编译器隐藏了,所以无法访问。
利用开发人员命令提示工具查看对象模型
-
开始菜单 -> VS 2019 -> 开发人员命令提示符
-
跳转文件所在盘符
-
跳转文件所在路径
-
查看对象模型
cl /d1 reportSingleClassLayout类名 "文件名"
顺序类似于堆栈,父类先进栈,最后出栈。
**构建子类对象时,**先调用父类的构造函数,再调用子类的构造函数。
**销毁子类对象时,**先调用子类的析构函数,再调用父类的析构函数。
- 访问子类:直接访问
son.m_A
- 访问父类:加作用域
son.father::m_A
只要子类中出现父类的同名成员函数,即使参数不同,发生重载,编译器也会将父类中的所有同名函数隐藏,不可访问。要想访问,需要加作用域。
静态同名成员与非静态同名成员,访问方式相同。除了通过以上对象的方式访问,还可以通过类名访问。
-
访问子类:直接访问
son::m_A
-
访问父类:加作用域
father::m_A
-
通过子类访问父类:
son::father::m_A
表示:son 类中 father 作用域下的m_A变量。
语法:class son: public father1, protected father2 {}
- 实际开发中,不建议多继承。
多继承可能会引发父类中有同名成员出错,需要加作用域区分。
也称为钻石继承。继承关系如图所示。
class Animal{
public:
int age;
};
class Sheep: public Animal {};
class Tuo: public Animail {};
class SheepTuo: public Sheep, public Tuo {};
- 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
- 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
class Animal{
public:
int age;
};
class Sheep: virtual public Animal {};
class Tuo: virtual public Animail {};
class SheepTuo: public Sheep, public Tuo {};
在继承的父类前加 virtual
关键字,使之成为虚继承,此时,被继承的父类称为虚基类。
虚继承仅继承下一份同名的基类成员和虚基类指针(vbptr)。虚基类指针指向虚基类表(vbtable)。虚基类表中记录了虚继承同名变量的偏移量,使得子类可以正确找到继承的唯一一份数据。
多态分为静态多态和动态多态。静态多态即重载,函数重载和运算符重载。动态多态是派生类和虚函数实现时多态。
静态多态和动态多态的区别:
多态 | 绑定函数地址 | 确定函数地址 |
---|---|---|
静态多态 | 早绑定 | 编译阶段确定 |
动态多态 | 晚绑定 | 运行阶段确定 |
函数重载是早绑定,在编译阶段就确定了函数地址。此时,doSpeak 传入的是 Animal。
class Animal{
public:
void speak(){
cout << "An Animail is speaking." << endl;
}
};
class Cat: public Animal{
public:
void speak(){
cout << "A cat is speaking." << endl;
}
};
void doSpeak (Animal &animal){
animal.speak();
}
int main() {
Cat cat;
doSpeak(cat); //An Animail is speaking.
return 0;
}
父类中使用虚函数,子类可以实现动态多态,函数地址晚绑定,使函数地址在运行阶段确定。此时,doSpeak 传入的是 Cat。
class Animal{
public:
virtual void speak(){
cout << "An Animail is speaking." << endl;
}
};
int main() {
Cat cat;
doSpeak(cat); //A cat is speaking.
return 0;
}
函数地址晚绑定,使函数地址在运行阶段确定。父类的指针或引用,指向子类对象。
- 有继承关系
- 子类 重写 父类的虚函数,virtual 可写可不写
**重写:**函数名、返回值类型、参数列表 完全相同
父类内部存储 vfptr(虚函数指针) ,指向 **vftable(虚函数表)。**虚函数表内记录父类虚函数的地址。子类内部存储继承自父类的虚函数指针,指向父类的虚函数表,表内记录父类的虚函数地址。
当子类重写父类虚函数时,子类中的虚函数表会替换成子类重写父类虚函数的地址。**所以,**当父类指针或引用指向子类时,发生动态多态。
实现动态多态时,当子类中有属性在堆区中开辟了内存,而释放父类指针时,无法正常调用子类的析构函数,导致内存泄漏。
而虚析构可以解决这个问题。在父类析构函数前添加 virtual
关键字。
class base{
public:
virtual callName () = 0;
}
class son: public base{
public:
string *name;
son (string n){
name = new string(n);
}
callName (){
cout << *name << endl;
}
~son(){ //未调用
if (name!=NULL){
delete name;
name = NULL;
}
}
}
int main(){
base* b = new son("Tom");
delete b;
return 0;
}
语法:virtual ~类名() {}
类中包含纯虚函数 或 纯虚析构 的类,称为抽象类。
语法:virtual void speak (int a) = 0;
- 无法实例化对象
- 子类必须重写抽象类的的纯虚函数,否则也属于抽象类
语法:virtual ~类名() = 0;
类名::~类名() {}
- 需要具体函数实现
- 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
C++中的泛型编程思想,使代码复用率大大提升。
template<typename T> //声明模板,T为类型名
void mySwap (T &a, T &b){
T tmp = a;
a = b;
b = tmp;
}
int main() {
int a = 10, b = 20;
mySwap(a,b); //自动类型推导
mySwap<int>(a,b); //显示指定类型
}
**注意:**声明后必须紧跟 一个 函数模板
- 普通函数可以发生 隐式类型转换
- 函数模板在 自动类型推导 时,不可以发生 隐式类型转换
- 函数模板在 显示指定类型 时,可以发生 隐式类型转换
-
当普通函数与模板函数相同时,优先调用普通函数
-
若想强制调用函数模板,可以使用空模板参数
mySwap<>(a,b);
-
函数模板也可以发生 重载
-
当函数模板更匹配参数时,优先调用函数模板
当传入的特殊参数类型会使模板函数错误时,使用具体化模板解决。
可以不用紧接着模板函数。
template<> void isEqual (person &a, person &b){
//具体实现
}
类的模板,函数模板是函数的模板。
template<class NameType, class AgeType>
class person{
public:
NameType m_Name;
AgeType m_Age;
person(NameType name, AgeType age){
this->m_Name = name;
this->m_Age = age;
}
}
int main() {
person<string,int> p1("aa",19);
return 0;
}
注意:
- 类模板中的成员函数,在运行时创建
- 生成类模板对象时,必须显示指定数据类型
- 类外实现成员函数时,作用域的类名后面必须加上模板参数
person<NameType, AgeType>::person(){}
-
类模板没有自动类型推导,必须指定类型
person p1("aa",11); //wrong
-
类模板的参数列表中,可以使用默认参数
template<class NameType, class AgeType = int> class person{ ... } int main() { //可以不指定具有默认参数的数据类型 person<string> p("aa",11); return 0; }
示例中的类如上文所示。共三种方式,第一种最为常用。
void printPerson (person<string,int> &p){
...
}
定义函数前,需要声明模板参数,让编译器知道T1,T2
template<class T1,class T2>
void printPerson (person<T1,T2> &p){
...
}
直接将类模板的类型作为 模板,定义方法类似于函数模板
template<class T>
void printPerson (T &p){
...
}
当继承一个类模板时,可以使用两种方式。
- 显示指定父类模板的数据类型
- 继承时声明模板
template<class T>
class base {
T m;
};
class son1: public base<int> {
};
template<class T1, class T2>
class son2: public base<T1> {
T2 a;
}
int main() {
son1 s1;
son2<int,char> s2; //此时,T1=T为int, T2为char
}
头文件:#inlcude <fstream>
文件分为:文本文件和二进制文件
- 包含头文件
#inlcude <fstream>
- 创建流对象
ofstream ofs;
- 打开文件
ofs.open("文件路径", 打开方式);
- 写数据
ofs << "写入的数据";
- 关闭文件
ofs.close();
步骤大致如写文件。
打开文件要判断 是否打开成功:ifs.is_open();
文件流对象:ifstream ifs;
//first, devided by space
char buf[1024] = {0};
while ( ifs>>buf )
cout << buf << " ";
//second, devided by line, includes space
char buf[1024] = {0};
while ( ifs.getline(buf,sizeof(buf)) )
cout << buf << endl;
//third, devided by line, includes space
string buf;
while ( getline(ifs,buf) )
cout << buf << endl;
ofstream ofs;
ofs("person.txt",ios::out|ios::binary);
Person p = {"aa",18};
ofs.write((const char*)&p,sizeof(Person));
ofs.close();
ifstream ifs;
ifs.open("person.txt",ios::in|ios::binary);
if ( !ifs.is_open() ){
cout << "error" << endl;
}
Person p;
ifs.read((char*)&p,sizeof(Person));
cout << p.name << " " << p.age << endl;
ifs.close();
打开方式 | 解释 |
---|---|
ios::in | 读文件 |
ios::out | 写文件 |
ios::ate | 初始位置:文件末尾 |
ios::app | 追加方式写文件 |
ios::trunc | 如果文件存在,先删除,再创建 |
ios::binary | 二进制方式 |
注意:打开方式可以同时使用
用法:ios::binary | ios::out
绝对路径的斜杠:/
当函数代码比较简单,又需要频繁调用时,系统开销会很大。此时可以使用内联函数,告诉编译器,将函数体插入代码中,从而减少开销。
在函数返回值前加关键字 inline
inline int getMax( int a, int b ){
return a>b ? a : b;
}
int main() {
int a = 2, b = 3;
cout << getMax(a,b) << endl;
return 0;
}
因为宏定义是在预编译阶段,单纯将代码段插入进主体,而不是像函数一样,进行传参操作。若使用不当,容易出现二义性。并且宏定义不会进行参数检查。
如:#define getMax(a,b) ((a)>(b)?(a):(b))
STL(Standard Template Library),标准模板库
- 从广义上分为,容器(container),算法(algorithm),迭代器(iterator)
- 容器和算法之间,通过迭代器进行无缝连接
- 六大组件:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
- **容器:**各种数据结构,如 vector、list、deque、set、map等
- **算法:**常用算法,如 sort、find、copy、for_each 等
- 迭代器:连接容器和算法
- 仿函数:
- 适配器:
- 空间配置器:
即常用数据结构:数组、链表、树、栈、队列、集合、映射表等。
容器分为序列式容器和关联式容器:
**序列式容器:**每个元素都有固定的位置,强调值加入的次序
**关联式容器:**二叉树结构,强调元素值的关系排序,元素没有固定的位置
-
容器:
vector
-
算法:
for_each
-
迭代器:随机访问迭代器
-
头文件:
#include <vector>
类似于数组,底层是一段连续的内存空间。当容量满了之后,会进行 动态扩展,寻找更大的内存空间,然后将原数据拷贝新空间,释放原空间。
-
创建:
vector<int> v;
vector<int> v(n); //大小为n
-
尾部插入元素:
v.push_back(10)
v.emplace_back(10);
-
删除尾部元素:
v.pop_back()
-
返回首元素指针:
v.data()
-
遍历容器:
//first //起始迭代器,指向第一个元素 vector<int>::iterator itBegin = v.begin(); //结束迭代器,指向最后一个元素的下一个位置 vector<int>::iterator itEnd = v.end(); while ( itBegin!=itEnd ){ cout << *itBegin << endl; itBegin++; } //second for ( vector<int>::iterator it=v.begin(); it!=v.end(); it++ ) cout << *it <<endl; //third //#include <algorithm> void myPrint (int val){ cout << val << endl; } for_each( v.begin(), v.end(), myPrint );
-
查看首个元素:
int first = v.front()
-
查看末尾元素:
int last = v.back()
-
访问 idx 所指数据:
v.at(idx)
v[idx]
-
互换容器:
v.swap(v1);
-
赋值:
v1 = v2;
v1.assign(v2.begin(), v2.end());
v1.assign(n,elem);
-
判断是否为空:
v.empty()
-
容器的容量:
v.capacity()
-
容器中元素的个数:
v.size()
-
重新指定容器中的元素个数,而容量不变:
v.resize(100)
若容器变长,以默认值填充新位置;若容器变短,删除末尾元素
若想以 elem 填充,则
v.resize(100, elem)
- 数组是静态空间,而vector可以动态扩展
- 动态扩展是,寻找更大的内存空间,然后将原数据拷贝到新空间,释放原空间
- 默认构造函数:
vector<T> v;
- 拷贝区间内的元素:
vector<int> v(v.begin(), v.end());
- 拷贝 n 个 elem:
vector<int> v(n, elem);
- 拷贝构造函数:
vector<int> v2(v1);
-
容量初始值为0,随着元素个数增多,动态扩展
-
vector<int>(v).swap(v);
收缩内存vector<int>(v)
是一个匿名对象, 通过 swap 交换容器,使 v 指向匿名对象,完成内存收缩,并回收多余空间 -
reserve(int len);
预留空间,减少动态扩展次数容器预留
len
个元素长度,预留位置不初始化,元素不可访问 -
v.end()
表示末尾元素的后一位,v.rend()
表示逆序末尾元素*(即首元素)的**后一位(即首元素的前一位)***。
-
容器:
string
-
头文件:
#include <string>
string 本质是一个类,类内部封装了 char*
- 创建一个空串:
string();
- 字符串初始化:
string (const char* s);
- 拷贝构造函数:
string (const string& str);
- 使用 n 个字符 c 初始化:
string (int n, char c);
-
赋值:
string s = "hello";
string s; s.assign("hello");
-
末尾拼接:
string s = "wo"; s += "c++";
string s = "wo"; s.append("c++");
string s = "wo"; s.push_back('c');
string s1 = "12"; string s2 = "3456"; s1.append( s2, 0, 3 ); 将s2从位置0开始的3个字符拼接到s1后面
-
插入:
s1.insert (1, "00"); //在s1的第1位前插入"00"
-
删除:
s1.erase(1, 3); //删除s1第1位起的3个字符
-
查找:
string s = "asdfg"; //int find(const string& str, int pos=0) const; //从s的第pos位开始查找str第一次出现的位置,默认第0位 //找不到,返回0 int pos = find(s);
rfind
查找最后一次出现的位置 -
替换:
string s = "abcdefg"; //从s的第1位字符起的3个字符,替换成"1111" s.replace(1,3,"1111"); //"a1111efg"
-
比较:
res = s1.compare(s2);
-
读取单个字符:
str[i]
str.at(i)
-
字符串大小:
str.size()
string 中结尾没有
'\0'
,大小为字符个数 -
获取子串:
s2 = s1.substr( 1, 3 ); //s1从第1位开始3个字符大小的子串
- 迭代器:双向迭代器,只支持前移和后移
双向循环链表。插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的。
- 默认构造:
list<T> lst;
- 拷贝构造:
list<int> l1(l2);
- 拷贝元素构造:
list<int> l1(l2.begin(), l2.end());
- 初始化元素构造:
list<int> L(n,elem);
-
赋值:
L1 = L2;
L1.assign(n, elem);
L1.assign(L2.begin(), L2.end());
-
交换容器:
L1.swap(L2);
-
元素个数:
L.size()
-
容器是否未空:
L.empty()
-
重新指定容器长度:
L.resize(num);
L.resize(num, elem);
默认以默认值填充,若容器变短,则删除超出长度的元素
-
插入元素:
push_back(elem)
push_front(elem)
insert(pos.elem)
等 -
删除元素:
pop_back()
pop_front()
erase(pos) //返回下一个数据的位置
-
删除所有与 elem 匹配的元素:
remove(elem)
-
查看元素:
L.front()
L.back()
-
反转:
L.reverse();
-
排序:
L.sort();
堆栈,没有迭代器
- 默认构造:
stack<T> stk;
- 拷贝构造:
stack(const stack &stk);
- 添加元素:
push(elem);
- 弹出元素:
pop();
- 返回栈顶元素:
top();
- 是否为空:
empty();
- 返回栈的大小:
size();
队列,没有迭代器
- 默认构造:
queue<int> q;
- 拷贝构造:
queue<int> q1(q2);
- 使用第二个参数构造:
- 访问队首:
q.front()
- 访问队尾:
q.back()
- 入队:
q.emplace(1)
q.push(1)
- 出队:
q.pop();
- 返回元素个数:
q.size()
- 队列是否为空:
q.empty()
- 交换队列:
q.swap(q1);
-
头文件:
#include <deque>
-
迭代器:随机访问迭代器,只读
双端数组,头部插入和删除的效率比 vector 快,但是访问元素的速度比 vector 慢。deque 内部是一个 中控器,中控器中维护着缓冲区地址,缓冲区中存放着真实数据,使得使用 deque时像一段连续的内存空间。
- 默认构造:
deque<T> d;
- 拷贝构造:
deque<int> d1(d2);
- 将
[beg,end)
区间内的元素拷贝:deque<int> d1(d2.begin(), d2.end());
- 拷贝 n个elem元素:
deque<int> d(10, 100);
- 赋值:
d1 = d2;
d2.assign(d1.begin(), dd1.end());
d2.assign(10,100);
- 是否为空:
deque.empty()
- 元素个数:
d.size()
- 重新指定容器长度(默认以0填充):
d.resize(num);
d.resize(num, elem);
- 两端插入:
push_back(elem)
push_front(elem)
pop_back()
pop_front()
- 指定位置插入:
insert(pos, elem)
- 清空数据:
d.clear();
- 清除数据:
erase(beg, end)
erase(pos)
- 取值:
d.at(index)
d[index]
d.front()
d.back()
- 头文件:
#inlcude <set>
set 中所有元素在插入时都会自动排序,属于关联式容器,底层结构使用 红黑树 实现。set 容器中不允许有重复元素。
- 默认构造:
set<T> st;
- 拷贝构造:
set( const set &st);
-
插入:
st.insert(10);
pair<iterator,bool> insert (const value_type& val);
返回插入结果,表示插入是否成功
-
删除:
删除 pos
迭代器所指元素,返回下一个元素的迭代器
it = erase(pos);
删除区间元素,返回下一个元素的迭代器
it = erase( beg, end );
删除容器中值为 elem
的元素:erase(elem);
-
清空所有元素:
st.clear();
-
返回元素数目:
st.size()
-
容器是否为空:
st.empty()
-
交换容器:
s1.swap(s2);
-
查找
key
:st.find(key)
若存在,返回该
key
的迭代器;若不存在,返回set.end()
-
统计
key
的个数:st.count(key)
set 容器默认从小到大排序
利用仿函数,自定义规则:
#include <set>
class myCmp{
public:
bool operator()(int v1, int v2)
return v1>v2;
};
int main(){
set<int,myCmp> st;
return 0;
}
**注意:**存放自定义类型时,必须指定排序规则,才可以插入数据
头文件:#include <map>
形式:map<key,value>
迭代器:双向迭代器
底层由 红黑树 实现。存储的对组 pair。map 存储 key 和 value,为 关联式容器,按照 key 从小到大排序,key值是唯一的,不允许出现重复元素。
- 插入:
m.insert(pair<int,int>(1,10));
- 拷贝构造:
map<int,int> m2(m);
- 赋值:
m1 = m2;
- 交换容器:
m1.swap(m2);
-
map 重载了
[]
,不可用数组下标访问元素,而是以 key 访问map[key]
。若无该 key,则返回0
或""
(value的数据类型) -
[]
可以添加新元素。若"abc"
不存在,则添加对组{"abc", 10}
myMap["abc"] = 10;
-
myMap.at("abc")
也可以取值。**不同于[]
的是,若查找失败,不会添加新元素,而是会抛出 ·out_of_range` 异常
-
map 中 key 值会被
const
修饰,不得被修改 -
可以自定义排序规则,C++提供了模板
map<int,string,greater<int>>
:从大到小排序map<int,string,less<int>>
:从小到大排序 -
自定义排序规则如 set
unordered<int,int> hashtable;
即哈希表,内部元素无序
操作:
hashtable.find(5)
查找指定元素;若没找到,返回hashtable.end()
hashtable[5]
访问元素
map, set, multimap, and multiset
插入、查看、删除:O(logn)
hash_map, hash_set, hash_multimap, and hash_multiset
插入、查看、删除:O(1),最坏情况 O(n)
用有限的步骤,解决问题,称为算法。
算法分为质变算法和非质变算法:
**质变算法:**改变元素内容的算法。如 拷贝、替换、删除等
**非质变算法:**不改变元素内容的算法。如 查找、计数、遍历、寻找极值等
类中重载了 函数调用()
符号,实例化后的对象,称为函数对象,也称为仿函数。函数对象可以和普通函数一样调用,可以作为参数传递,但是本质上是一个类,它可以拥有自己的状态。
class MyAdd {
public:
int count;
MyAdd() {
//设置函数对象的状态
count = 0;
}
int operator() (int v1, int v2) {
count++;
return v1 + v2;
}
};
void printAdd (MyAdd &mp, int v1, int v2) {
cout << mp(v1,v2) << endl;
}
int main() {
MyAdd myAdd;
//调用仿函数
cout << myAdd(1,2) << endl;
cout << myAdd(2,3) << endl;
cout << myAdd(3,4) << endl;
//输出函数对象的状态
cout << myAdd.count << endl;
//函数对象作为参数传递
printAdd(myAdd,4,5);
return 0;
}
当函数对象重载 ()
的返回值为 bool
类型时,函数对象称为谓词。若仿函数接受一个参数,则称为一元谓词;若接受两个参数,则称为二元谓词。
谓词以匿名对象的形式,作为函数的形参
class MyCompare{
bool operator() (int v1, int v2) {
return v1 > v2;
}
};
int main() {
vector<int> v;
v.push_back(30);
v.push_back(10);
v.push_back(20);
//自定义排序规则
sort(v.begin(), v.end(), MyCompare());
return 0;
}
头文件:#include <functional>
内建函数对象是STL中的一些已经存在,可以使用的函数对象。
功能是实现四则运算。二元仿函数的数据类型相同,所以只需传递一个模板参数。
#include <functional>
int main() {
plus<int> p;
minus<int> m;
multiplies<int> multi;
divides<int> d;
modulus<int> mod;
negate<int> n;
cout << p(1,2) << endl;
cout << m(2,1) << endl;
cout << multi(2,3) << endl;
cout << d(6,2) << endl;
cout << mod(9,2) << endl; //取余
cout << n(2) << endl; //取相反数
}
功能是实现关系对比。返回值均为 bool。
- 等于:
equa_to<T>
- 不等于:
not_equal<T>
- 大于:
greater<T>
- 大于等于:
greater_equal<T>
- 小于:
less<T>
- 小于等于:
less_equal<T>
功能是实现逻辑运算,与或非。返回值均为 bool。
- 逻辑与:
logical_and<T>
- 逻辑或 :
logical_or<T>
- 逻辑非:
logical_not<T>
当使用 rand()
函数时,每次运行的随机数都是相同的。这时,需要添加随机数种子。
#include <ctime>
srand((unsigned int)time(NULL));
如此,每次运行产生的随机数都会根据时间变化。
头文件:#include <algorithm>
- 功能:遍历容器
- beg:起始迭代器
- end:末尾迭代器
- _func:函数或者函数对象,对每一个元素的操作
- 语法:
for_each(iterator beg, iterator end, _func);
- 注意:遍历自定义类型数据时,_func必须为 谓词 。
void func1(int elem);
class func2 {
public:
void operator()(int elem) {
cout << elem << " ";
}
};
void func1(int elem) {
cout << elem << " ";
}
int main() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.emplace_back(i + 1);
}
//使用函数
for_each(v.begin(), v.end(), func1);
cout << endl;
//使用函数对象
for_each(v.begin(), v.end(), func2());
cout << endl;
return 0;
}
-
功能:将源容器的元素搬运至目标容器
-
beg1:源容器起始迭代器
-
end1:源容器结束迭代器
-
beg2:目标容器起始迭代器
-
_func:函数或函数对象,使源容器中的元素经过 func 再搬运至目标容器
-
语法:
transform(iterator beg1, iterator end1, iterator beg2, _func);
-
注意:搬运之前必须在目标容器开辟空间
int func1(int elem) {
return elem*2;
}
class func2 {
public:
void operator()(int elem) {
cout << elem << endl;
}
};
int main() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.emplace_back(i + 1);
}
vector<int> tv;
//目标容器需要提前开辟空间
tv.resize(v.size());
transform(v.begin(), v.end(), tv.begin(), func1);
for_each(tv.begin(), tv.end(), func2());
return 0;
}
头文件:#include <algorithm>
- 功能:在容器中查找指定元素。若找到,则返回元素的迭代器;若没找到,则返回结束迭代器。
- 语法:
find(iterator beg, iterator end, value);
- 注意:在查找 自定义数据类型 时,需要在类中重载
==
操作符,参数要加const
。
#include <algorithm>
class Person {
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
//重载==,因为编译器无法直接比较自定义数据类型
bool operator==(const Person& p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
return false;
}
string m_Name;
int m_Age;
};
int main() {
vector<Person> v;
for (int i=0; i<5; i++)
v.emplace_back(Person("aa", 10+i));
auto it = find(v.begin(), v.end(), Person("aa", 20));
if (it != v.end()) {
cout << (*it).m_Name << " " << (*it).m_Age;
}else{
cout << "Not Find!";
}
cout << endl;
return 0;
}
- 功能:在容器中查找某个符合指定条件的元素。找到,则返回元素迭代器;没找到,则返回结束迭代器。
- 语法:
find_if(iterator beg, iterator end, _Pred);
- _Pred:函数或者谓词,表示查找条件。
- 功能:查找相邻的重复元素。若找到,则返回相邻元素的第一个迭代器;若没找到,则返回结束迭代器。
- 语法:
adjacent_find(iterator beg, iterator end);
- 示例:1,2,3,3,2,5。在这个序列中查找,结果为3的第一个迭代器。
- 功能:在 有序 容器中查找指定元素。若找到,返回 true;否则,返回 false。
- 语法:
bool binary_search(iterator beg, iterator end, value);
- value:指定元素,需要与容器类型相同。
- 功能:统计容器中存在指定元素的个数。
- 语法:
count(iterator beg, iterator end, value);
- 注意:在统计 自定义数据类型 时,需要在类中重载
==
操作符,参数要加const
。
- 功能:有条件地统计元素个数。
- 语法:
count_if(iterator beg, iterator end, _Pred);
- _Pred:谓词或函数,表示统计的条件。
头文件:#include<algorithm>
- 功能:排序容器内元素
- 语法:
sort(iterator beg, iterator end, _Pred);
- _Pred:谓词或函数,表示排序规则。
- 功能:将容器内的元素随机调整次序
- 语法:
random_shuffle(iterator beg, iterator end);
- 功能:将两个 有序 容器合并,有序 存放到目标容器中。
- 语法:
merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
- 注意:目标容器必须有足够空间
- 功能:将容器中元素反转。
- 语法:
reverse(iterator beg, iterator end);
头文件:#include <algotithm>
- 功能:将源容器区间内的元素拷贝至目标容器中
- 语法:
copy(iterator beg, iterator end, iterator dest);
- 注意:目标容器必须有足够空间
- 功能:将容器区间内的元素替换为新元素
- 语法:
replace(iterator beg, iterator end, oldvalue, newvalue);
- 注意:若元素类型为自定义数据类型,则必须在类中重载
==
;若新元素为自定义数据类型中的基本数据类型,则需要重载=
操作符
- 功能:替换容器区间内满足条件的元素
- 语法:
replace_if(iterator beg, iterator end, _pred, newvalue);
- _Pred:谓词或函数,表示条件规则。
- 注意:若新元素为自定义数据类型中的基本数据类型,则需要重载
=
操作符
- 功能:互换两个容器的元素
- 语法:
swap(container c1, container c2);
- 注意:两个容器的类型必须完全相同。
头文件:#include <numer>
- 功能:累加容器内元素,即求和
- 语法:
accumulate(iterator beg, iterator end, value);
- beg:起始迭代器
- end:结束迭代器
- value:累加的初始值
另外,使用自定义类型时:
需要定义一个函数对象或者普通函数,作为第四个参数传入。定义的函数需要两个参数,具体如下。
#include <numeric>
class person {
public:
int age;
static int cnt;
person() {
cout << "person: " << this->cnt++ << endl;
this->age = 10 + cnt;
}
};
int person::cnt = 0;
//定义函数对象或函数
class func2 {
public:
int operator()(int init, person p) {
return init + p.age;
}
};
int func1(int init, person p) {
return init + p.age;
}
int main() {
vector<person> p(10);
cout << accumulate(p.begin(), p.end(), 0, func1) << endl;
cout << accumulate(p.begin(), p.end(), 0, func2()) << endl;
return 0;
}
- 功能:填充元素
- 语法:
fill(iterator beg, iterator end, value);
- beg:起始迭代器
- end:结束迭代器
- value:填充的元素,类型必须与容器相同或者可以进行强制类型转换的类型
头文件:#include <algorithm>
- 功能:求两个 有序 容器的交集,使用前需要开辟足够空间
- 语法:
set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
- beg1,end1:容器1的起始和结束迭代器
- beg2,end2:容器2的起始和结束迭代器
- dest:目标容器的起始迭代器
- 返回值:指向交集 末尾元素 的后一个元素的迭代器
- 功能:求两个 有序 容器的并集,使用前需要开辟足够空间
- 语法:
set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
- 用法均与
set_intersection
类似
- 功能:求两个 有序 容器的差集,使用前需要开辟足够空间
- 语法:
set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
- 用法均与
set_intersection
类似
提供一种方法,使之可以依序访问容器中的各个元素,而又无需暴露该容器的内部表达方式。**算法通过迭代器访问容器中的元素。**迭代器的使用类似于指针。
每个容器都有自己专属的迭代器。
常用的迭代器为:双向迭代器和随机访问迭代器
种类 | 功能 | 支持运算 |
---|---|---|
输入迭代器 | 对数据只读 | ++、== |
输出 | 对数据只写 | ++ |
前向 | 读写,并能向前推进 | ++、==、!= |
双向 | 读写,并能向前向后操作 | ++、-- |
随机访问 | 读写,可以跳跃访问任意数据 | ++、--、[n]、-n、<、<=、>、>= |
https://www.nowcoder.com/discuss/840768?toCommentIpt=1
序列式容器,定义时必须指定大小,且大小固定,大小是容器类型的一部分。
定义:array<int, 10> arr;
头文件:#include <memory>
为防止没有及时有效地释放已分配的内存,造成内存泄露,引入了智能指针。智能指针指向动态分配的内存,能够确保变量在离开作用域时,自动销毁指针。智能指针的核心实现技术是引用计数。
独占的智能指针。unique_ptr
类型的指针指向的内存,只能由该指针管理。所以 引用计数 只能为1。
共享的智能指针。多个智能指针可以同时管理一块内存,引用计数为零时,销毁指针。
弱引用的智能指针。它不共享指针,不能操作内存,是用来监视 shared_ptr
的。
容器的插入函数,相较于 push_back()
的效率更高
push_back()
:先调用 构造函数 创建一个新元素的临时对象,再调用 移动构造函数 移动到容器中
emplace_back()
:在原地调用 构造函数 创建新元素,省去了调用移动构造函数的过程
二者都是类型说明符,自动推导变量的类型。
auto b = 3.14;
auto a = 2;
auto 一般会忽略掉顶层 const,会自动将推导的数组类型转换为指针类型
decltype(f()) sum = x; sum的类型的 f()的返回类型
decltype
不会忽视变量的顶层 const和引用。decltype
的表达式,如果外层加了括号,则会得到引用类型。如果表达式是解引用操作,那么也会得到引用类型。