C++面向过程
C++简介
#iclude <iostream>
using namespace std;//命名空间
int main(void)
{
cout << "hello" << endl;
// cout 输出对象 << 输出插入运算符 endl 相当于\n
return 0;
}
g++ hello.cpp -o hello
输入输出流
- 输入流
- 从输入设备流向内存的字节序列
cout自动解析变量的数据类型
- 输出流
- 从内存流向输出设备的字节序列
- 当程序执行到cin时,就会停下来等待键盘的输入
- cin析取运算符
名字空间
- 语法格式
namespace xxx_name{
members;
}
- 使用名字空间
- 使用作用域限定符::
ns1::func();
- 使用using引用名字空间单个成员
using ns1::func;
func();
ns2::func();
- 使用using引用名字空间全部成员
using namespace ns1;
func();
cout << a << endl;
ns2::func();
- 名字空间嵌套
namespace ns1{
int j = 0;
namespace ns2{
int i = 1;
}
}
数据类型的扩展
- 结构体
- C++中定义结构体变量时,可以省略struct关键字
- C++结构体中可以直接定义函数,称为成员函数
struct stu{
int num;
void func(void)
{
cout << "hello" << endl;
}
};
stu s1;
s1.num = 21;
s1.func();
- 联合体
- C++中定义联合体变量时,可以省略union关键字
- 支持匿名联合
union{
....
};
- 枚举
- C++中定义枚举变量时,可以省略enum关键字
- C++中枚举是独立的数据类型,不可以当作整型来使用
- 布尔
- C++中专门表示逻辑值
- true 表示真
- false 表示假
- 本质就是单字节的整数
- 任何基本类型都可以隐式的转换为布尔类型
cout << b << endl;//输出1
cout << boolalpha << b << endl;//输出true
- 字符串
- C++兼容C的字符串方法和函数
- C++专门设计了string类型表示字符串
类型转换
-
隐式类型转换
- 隐式类型规则
- 隐式类型规则
-
显示类型转换
- 强制类型转换
float c = (float)a;//c float d = float(a);//c++
- C++提供了四个强制类型转换符
//强制类型转换符 static_cast dynamic_cast const_cast reinterpret_cast
- static_cast
目标类型变量 = static_cast<目标类型>(源类型变量)
-
用于隐式转换的逆转换,常用于
基本数据类型之间
的转换、void* 转换为其它类型
的指针 -
不能用于
整型
和指针
之间的互相转换,不能
用于不同类型的指针、引用
之间的转换
- reinterpret_cast重解释类型转换
- 用于任意类型指针或引用之间的转换
- 指针和整型数之间的转换
- const_cast
- 用于去除指针或引用的常属性
- dynamic_cast
- 主要用于多态中类指针的向下转型,可以检测是否可以转型成功
引用
- 引用:给变量起别名
- 语法
数据类型 &别名=原名;
int &a=b;
-
注意
-
引用必须初始化
-
一但初始化后不能更改
-
-
引用做函数参数(引用传递)
- 不要返回局部变量的引用
- 函数的调用可以作为左值(如果函数的返回值是引用,这个函数调用可以做左值)
-
本质
- 内部是一个指针常量
int a = 10;
int &ref = a;
->
int * const ref = &a;
->
int &ref = a;
_________________
*ref = 20;
->
ref = 20;
- 常量引用
int &ref =10;//不可以,引用必须引用一块合法的内存空间
const int &ref = 10;//加上const后,编译器做了修改
->
int temp = 10;
int &ref = temp;
区别:
- 引用:
- 引用是一个变量的别名,一旦被初始化,它就绑定到一个有效的对象,并且不能改变这个绑定。
- 引用在定义时必须初始化,且初始化后不能改变指向的对象。
- 引用在形式上更漂亮,使用引用指向的内容时可以直接使用引用变量名,而不像指针一样需要解引用操作。
- 引用在使用时不需要进行NULL检查,因为引用不会指向NULL。
- 指针:
- 指针是一个存储地址的变量,可以指向任何有效的内存地址。
- 指针在定义时不必立即初始化,可以在后续任何地方赋值。
- 指针在使用时需要加“*”进行解引用操作。
- 指针可以指向空值,而引用必须初始化。
- 指针在使用时需要进行NULL检查,以确保指针指向有效对象。
- 其他区别:
- 程序为指针变量分配内存区域,而不为引用分配内存区域。
- 引用在创建时初始化,而指针在定义时不必马上初始化。
- 引用不会指向NULL,而指针可以指向NULL。
- 引用比指针使用起来更安全,因为引用不存在空引用,且一旦初始化就指向一个对象,不能改变绑定的对象。
C++函数的缺省参数
函数默认参数
void func(int a,int b = 20,int c = 30)//当传递参数传参了,使用传参,没有则使用默认值
- 注意
- 如果某个位置上有了默认参数,那么这个位置从左往右都必须有默认参数
- 如果函数声明中有默认参数,函数实现就不能有默认参数,声明和实现只能有一个有默认参数
哑元(函数占位符)
- 参数列表中可以有占位参数,用来占位,调用函数时必须填补该位置
- 语法
void TestFunc01(int a,int b,int){
//函数内部无法使用占位参数
cout << "a + b = " << a + b << endl;
}
//占位参数也可以设置默认值
void TestFunc02(int a, int b, int = 20){
//函数内部依旧无法使用占位参数
cout << "a + b = " << a + b << endl;
}
int main(){
//错误调用,占位参数也是参数,必须传参数
//TestFunc01(10,20);
//正确调用
TestFunc01(10,20,30);
//正确调用
TestFunc02(10,20);
//正确调用
TestFunc02(10, 20, 30);
return EXIT_SUCCESS;
}
函数重载
-
作用:函数名可以相同,提高复用性
-
实现:编译器将形参变量的类型作为最终函数名的一部分
-
条件
- 同一作用域下
- 函数名相同
- 函数参数类型不同,或者个数不同,或顺序不同
-
优先级
-
完全匹配 //最高
-
常量转换 //较好
- 将变量转为常量
-
升级转换 //一般
-
降级转换 //较差
-
省略号匹配 //最差
-
-
注意
- 函数的返回值不可以作为函数的重载条件
引用作为函数的条件时注意有无const(加const和不加const也能作为重载条件)
- 函数重载遇到默认参数,二义性
- 形参变量名不同不构成重载的要素
- 函数返回类型不同不构成重载的要素
#include<iostream>
using namespace std;
void fun(int a)
{
cout<<"1"<<endl;
}
void fun( int a,int b = 10)//void fun( int a,int b)正确
{
cout<<"2"<<endl;
}
int main()
{
fun(10);//两个函数都能调用
return 0;
}
内联函数
- 在函数声明或定义时,将inline关键字加在函数返回类型前面就是内联函数
- 不需要建立函数调用时的运行环境,不需要进行参数传递 , 不需要跳转
- 注意
- inline关键字只是建议编译器做内联优化,编译器不一定做内联
- 内联函数的声明或定义必须在函数调用之前完成
- 一般几行代码适合作为内联函数,像递归函数 包含循环 switch goto复杂逻辑的函数不适合内联
动态内存管理
-
new和delete
-
new的语法
-
p = new int;//分配一个int内存空间 p = new int(123);//分配一个int内存空间并初始化为123 p = new int[123];//分配123个int内存空间
-
-
free语法
-
delete(p); delete []p;//释放数组
-
C++面向对象
类与对象
类声明的语法
class 类名
{
private:
私有的数据和成员函数
public:
公用的数据和成员函数
protected:
保护的数据和成员函数
};
- public 公共权限
- 成员在类内可以访问,类外也可以访问
- protected
- 成员在类内可以访问,类外不可以访问
- private
- 成员在类内可以访问,类外不可以访问
- 和struct的区别
- 默认的权限访问权限不同
- class默认私有
- struct默认公共
对象的初始化和清理
-
构造函数
- 主要作用与创建对象时对象成员属性赋值,构造函数由编译器自动调用无需手动
- 没有返回值,不需要void
- 函数名与类名相同
- 可以有参数,可以发生重载
- 自动调用析构函数,无需手动调用,只会调用一次
-
析构函数
- 主要作用于对象销毁前系统自动调用,执行一些清理工作
- 不可以有参数,不可以重载
- 构造和析构都是必须有的实现,如果我们不提供,编译器会提供一个空实现的析构和构造函数
- 只能由系统调用,不能显示调用
- 栈对象,离开其作用域时析构函数自动调用
- 堆对象,执行delete操作时析构函数自动调用
- 对象的创建和销毁的过程
- 对象创建
- 分配内存
- 构造成员对象
- 调用构造函数
- 对象销毁
- 调用析构函数
- 析构成员对象
- 释放内存
- 对象创建
-
语法
类名(){}//构造函数
~类名(){}//析构函数
构造函数的重载
某些重载的构造函数具有特殊的含义:
-
缺省构造函数: 按缺省方式构造
-
类型转换构造函数: 从不同类型的对象构造
-
拷贝构造函数:从相同类型的对象构造
类型转换构造函数
- 转换构造函数只有一个参数
- explicit关键字,遇到需要类型转换时,强制写成如下形式
Integer b = Interger(1);
//Integer b = 1;err
深拷贝和浅拷贝
-
浅拷贝
- 简单的复制拷贝(堆区内存重复释放)
-
深拷贝
- 在堆区重新申请空间进行拷贝(如果类的属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝的发生)
-
语法
#include<iostream>
using namespace std;
class Complex
{
public:
double real, imag;
Complex(double r,double i)
{
real = r; imag = i;
}
Complex(const Complex & c)//拷贝构造函数
{
real = c.real; imag = c.imag;
cout<<"Copy Constructor called"<<endl ;
}
};
int main()
{
Complex c1(1, 2);
Complex c2(c1); //调用复制构造函数
cout<<c2.real<<","<<c2.imag;
return 0;
}
拷贝函数的调用时机
- 使用一个已经创建完毕的对象来初始化一个新的对象
- 值传递的方式给给函数参数传值
- 以值的方式反回局部对象
拷贝赋值
#include<iostream>
using namespace std;
class Complex
{
public:
double real, imag;
Complex(double r,double i)
{
real = r; imag = i;
}
Complex(const Complex & c)//拷贝构造函数
{
real = c.real; imag = c.imag;
cout<<"Copy Constructor called"<<endl ;
}
//拷贝赋值
Complex &operator=(const Complex &that)
{
if(this != that)
{
delete m_pi;
m_pi = new int(*that.m+pi);
}
return *this;
}
};
初始化列表
- 作用:初始化属性
- 顺序:和声明的顺序相关
- 语法
构造函数():属性1(值1),...{};
语法:
class CExample
{
public:
int a;
float b;
//构造函数初始化列表
CExample(): a(0),b(8.8)
{}
//构造函数内部赋值
CExample()
{
a=0;
b=8.8;
}
};
例子:
//正确
A::A(int pram1, int pram2, int pram3):b(pram2),c(pram3)
{
a=pram1;
}
//错误
A::A(int pram1, int pram2, int pram3)
{
a=pram1;
b=pram2;
c=pram3;
}
- 注意
初始化const成员变量,只能用初始化列表
引用同样用初始化列表初始化
常量和引用初始化必须赋值
类对象做为类成员
- 先调用成员的构造函数,后调用类对象的构造函数
- 后调用类成员的析构,先调用类对象的析构函数
静态成员
-
C++中希望某个类的多个对象之间实现数据共享,可以通过static建立一个被局限在类中使用的全局资源,该类型资源被称为静态成员。 (可以理解为
局限在类中使用的全局变量
) -
静态成员函数
- 静态成员函数可以直接定义在类的内部,也可以定义在类的外部,这一点和普通的成员函数没有区别
- 静态成员函数没有this指针,没有const属性,可以把静态函数理解为被限制在类中使用的全局函数
- 静态成员函数中只能访问静态成员,但是在非静态成员函数中既可以访问静态成员也可以访问非静态成民
- 静态成员函数和静态成员变量一样,也要受到类的访问控制限定符的约束
-
静态成员变量
- 所有对象共享一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
成员对象
一个类的属性是另一个类的对象,那么称他为成员对象
C++的对象模型
- 只有非对象成员变量才属于类的对象
- 成员变量和成员函数是分开存储的
- 空对象占用内存空间为1字节,是为了区分空对象占内存的位置
this指针
-
本质:指针常量
-
作用:区分多个同类型的对象是哪个对象调用
-
用途
- 当形参和成员变量相同名字时,可以用this指针区分
- 在类的非静成员函数中返回对象本身可以选择使用
return *this
- this指针指向被调用的成员函数所属的对象`
空指针调用成员函数
空指针不可以访问属性
const修饰成员函数
- 成员函数
后
加const称为常函数 - 常函数内不可以修改成员属性
- 成员属性声明时加关键字
mutable
后,在常函数中依然可以修改
const修饰对象
- 声明对象前加const为常对象
- 不允许修改属性,除mutable声明外
- 常对象只能调用常函数,非常对象既可以调用非常函数 也可以调用常函数
- 函数名和形参表相同的成员函数,常版本和非常版本可以构成重载
- 常对象只能选择常版本
- 非常对象优先选择非常版本
友元
- 关键字:friend
- 目的:让一个函数或类访问私有成员
- 友元的三种实现
- 友元全局函数
- 友元类
- 一个类可以是另一个类的友元,友元类的所有成员函数都是另一个类的友元函数,能够直接访问另一个类的所有成员
- 需要在A类中声明B是A的友元类
- 友元类不是双向的
- 友元成员函数
单例模式
- 单例模式(Singleton Pattern,也称为单件模式),使用最广泛的设计模式之一。其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
- 面向对象编程中,每个对象都应该抽象代表一个设备,并通过对象完成对某个具体设备的管理和维护
- 实现单例模式的步骤
- 私有化构造函数
- 使用静态成员变量维护唯一的单例对象
- 定义静态成员函数用于获取单例对象
- 分类
- 饿汉式
- 加载进程时完成创建,用不用都创建
- 懒汉式
- 用时再创建,不用再销毁
- 饿汉式
- 思考
- 为什么不使用不同的成员函数?
- 调用一个函数的前提是先有一个对象,但是它又要作为创建对象的接口,这两个本来就是相悖的
- 为什么不使用不同的成员函数?
运算符重载
加号重载
返回类型 类名 operator+()
- 成员函数重载
//p1.operator+(p2)
const Complex operator+(const Complex& c)
{
Complex tmp(r+c.r,i+c.i);
return tmp;
}
- 友元函数重载
//operator+(c-a)
//在类中声明
friend const Complex operator-(const Complex& c);
const Complex operator-(const Complex& c)
{
Complex tmp(r+c.r,i+c.i);
return tmp;
}
- 注意:
- 表达式结果是右值
- 左右操作数既可以是左值也可以是右值
左移运算符重载
返回类型 类名 operator<<()
- 注意:
通常不会使用成员函数重载左移,因为无法实现cout再左边,只能利用全局函数重载
- 或者将operator<<()声明为友元
本质:operator<<(cout,p)
ostream & operator<<(ostream &out,Person &P)//返回引用
递增运算符重载
- 前置递增,返回引用
- 后置递增,返回值
- 用占位参数(哑元)来区分前置后置
operator++()//前置加加
operator++(mt)//后置加加
后置加加:1.先记录当前的结果2.后递增3.最后将记录的结果返回
赋值运算符重载
operator=()
Name& operator=(Name &obj1)//为了支持链式编程,所以返回对象的引用
{
//第一步:先释放旧的内存//以(obj3 = obj1;)为例,应该先释放obj3的内存,再分配内存大小,最后赋值
if (this->m_p != NULL)
{
delete[] m_p;
m_len = 0;
}
//第二步:根据obj1分配obj3内存大小
this->m_len = obj1.m_len;
this->m_p = new char[m_len + 1];
//第三步:把obj1拷贝给接受对象
strcpy(m_p, obj1.m_p);
return *this;//返回obj3元素
}
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_43293737/article/details/86104449
- 可以解决浅拷贝的问题
- 返回对象本身
- 注意:
- 表达式的结果是左值,就是左操作数的自身
- 左操作数必须是左值,右操作数可以是左值也可以是右值
函数调用运算符重载
operator()()
例:
Converter RMBtoUS(6,4);//Converter 一个类 RMBtoUS类声明的对象并作初始化
cout << RMBtoUS(10) << endl;//()重载之后 类似的调用函数称为仿函数
由于使用起来很类似函数调用,因此称为仿函数
关系运算符重载
operator==()
operator!=()
...
输入输出运算符重载
继承
-
作用:减少重复的代码
-
语法:
class 子类:继承方式 父类
-
注意:
- 基类的构造函数和析构函数不能继承
- 基类的友元函数不能继承
- 静态数据成员和静态成员函数不能继承
-
继承方式
- pubilc 公共继承
- protected 保护继承
- private 私有继承
- 父亲的私有成员被继承,但是不可以访问,内存也算
-
父类的构造函数要放在子类的初始化列表中来初始化
-
向上造型
- 将子类类型的指针或引用转换为基类类型的指针或引用; 这种操作性缩小的类型转换,在编译器看来是安全的,可以隐式转换。
-
向下造型
- 将基类类型的指针或引用转换为子类类型的指针或引用; 这种操作性放大的类型转换,在编译器看来是危险的,不能隐式转化,但是可以显式转换
-
继承中的构造和析构的顺序
- 先构造父类在构造子类
- 析构的顺序相反
-
继承中同名成员的处理方式
- 访问父类同名成员,需加作用域
- 如果子类成员中出现和父类同名的成员函数,子类的同名成员会隐藏父类中所有同名成员函数,静态成员也一样
-
访问同名函数
- 通过对象.函数名的方式来访问
- 通过类::函数名的方式来访问
-
多继承的语法
-
class 子类:继承方式1 父类1,继承方式2 父类2...
不建议使用
-
-
菱形继承
- 加上virtual为虚继承
- 类变为虚类
- 主要解决:子类继承两份相同的数据导致浪费
- 虚基类指向一个虚基类表
多态
- 多态的定义
- 如果子类提供了对基类虚函数有效的覆盖,那么通过指向子类对象的基类指针,或者通过引用子类对象基类引用,调用该虚函数,实际被执行将是子类中的覆盖版本,而不再是基类中原始版本,这种语法现象被称为多态
- 多态的意义
- 在于,一般情况下,调用哪个类的成员函数由调用者指针或者引用本身类型决定的,而有了多态,调用哪个类的成员函数由调用者指针或者引用实际目标对象的类型决定
- 这样一来,源自同一种类型的同一种激励,竟然可以产生多种不同的响应,也就是对于同一个函数调用,能够表达出不同的形态,即为多态。
- 多态的好处:
- 代码组织结构清晰
- 可读性强
- 利于前后期的扩展和维护
- 虚函数覆盖的条件
- 只有类中的成员函数才能声明为虚函数,而全局函数、静态成员函数、构造函数都不能被声明为虚函数
- 只有在基类中以virtual关键字声明的虚函数,才能作为虚函数被子类覆盖,而与子类中的virtual关键字无关
- 虚函数在子类中的版本和基类中版本要具有相同的函数签名,即函数名、参数表、常属性一致如果基类虚函数返回基本类型的数据,那么子类中的版本必须返回相同类型的数据;
- 如果基类虚函数返回类类型指针或用,那么允许子类中的版本返回其子类类型指或引用
- 产生多态的条件
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写,通过基类的指针或者引用调用虚函数
- 调用虚函数的指针也可以是this指针,当使用子类对象调用基类中的成员函数时,该函数里面this指针将是一个指向子类对象的基类指针,再通过this去调用满足重写要求的虚函数同样可以表现多态的语法特性
过基类引用调用基类和派生类中的同名虚函数时,若该引用指向的是一个基类对象,那么调用基类虚函数;反之,调用派生类的虚函数。
虚函数
- 被virtual关键字修饰的成员函数称为虚函数
- 实现多态性,即通过基类访问派生类的函数
纯虚函数
-
语法:
-
virtual 返回值类型 函数名 (参数列表)=0;
-
-
当类中有了纯虚函数,这个类也成为抽象类
-
子类必须重新写父类中的纯虚函数,否则无法实例化对象
虚析构和纯虚析构
- 语法:
virtual ~类名(){}//虚析构
virtual ~类名(){}=0;//纯虚析构
- 纯虚析构需在外定义
- 纯虚析构需要声明也需要定义,也属于抽象类,无法声明对象
注意
多态在使用时,如果子类中有属性开辟到堆区,那么父类指针无法调用子类的析构代码
解决方式:将父类中的析构代码改为虚析构或者纯虚析构
虚函数是如何实现的
每一个含有虚函数(无论是其本身的,还是继承而来的)的类都至少有一个与之对应的虚函数表,其中存放着该类所有的虚函数对应的函数指针
运行时类型信息
-
运行时类型信息(Run-time Type lnformation,RTTI) 提供了在程序运行时刻确定对象类型的方法,是面向对象程序语言为解决多态问题而引入的一种语言特性。由于多态的要求,C++指针或引用可能与他们实际代表的类型不一致 (如基类指针可以指向派生类对象),当将一个多态指针转换为其实际指向类型对象时,就需要知道对象类型信息。
-
在C++中,用于支持RTTI的运算符有
-
dynamic_cast
-
typeid
-
type_info
-
-
typedid和type_info
- typeid操作符既可用于类型也可用于对象,返回typeinfo对象的常引用,用于表示类型信息
- typeinfo类的成员函数name(),可以获取字符串形式的类型信息
- typeinfo类支持“= =”和“! =”操作符,可直接用于类型相同与否的判断,如果类型之间存在多态的继承关系,typeid还可以利用多态的特性确定实际对象的类型
//头文件
#include <typeinfo>
int i = 0;
typeid(i).name();
typeid(int).name();
转换函数
自定义转换函数
operator int()
{
return sec+min*60+hour*60*60;
}
int sec = t;//t是一个类声明的对象
隐式转换
class mempool
{
public:
explicit mempool(int size)
{
data = new char[size];
cout<< "cccc"<<endl;
}
~mempool()
{
delete[]data;
}
private:
char * data;
}
int main()
{
//mempool a(100);
mempool a = 100;//隐式类型转换了 加上explicit防止隐式类型转换
}
异常
- 异常是指程序运行期间发生的不正常情况,如new无法获得所需内存、数组下标越界、运算溢出、除0错误.无效参数以及打开文件不存在等。
- 异常处理就是指对程序执行过程中产生的异常进行适当的处理,避免程序出现丢失数据或破坏系统运行等灾难性后果。
- 跳转
- 优点:不需要逐层判断,一步到位,代码精炼
- 缺点:函数调用路径中的栈对象失去析构机会,存在内存泄漏风险
//头文件<csetjmp>
jmp_buf env;
setjmp(env);//保存当前栈快照
longjmp(env,-1);//跳转到setjmp位置执行
- C++引入了三个关键字
- try 用于检测可能发生的异常
- throw 用于抛出异常
- catch 用于捕获并处理由throw抛出的异常
try{
...
if err1 throw xx1;
...
if err2 throw xx2;
...
}
catch(type1 arg){...}
catch (type2 arg){...}
注意: catch在进行数据异常类型匹配时,不会进行数据类型的默认转换,只有与异常类型精确匹配的catch块才会被执行。
函数的异常说明
- C++允许限制函数能够抛出的异常类型,限制方法时在函数声明后面添加一个throw参数表,在其中指定函数可以抛出的异常类型。
int fun(int,char)throw(int,char);
- 函数fun被限定只允许抛出int和char类型的异常,当fun函数抛出其他类型的异常时,程序将被异常终止。如果函数不允许抛出任何异常,只需要指定throw限制表为不包括任何类型的空表。
int fun(int,char)throw();
标准异常类
IO流
- ios类是istream类和ostream类的虚基类,用来提供对流进行格式化I/O操作和错误处理的成员函数
- streambuf主要作为其他类的支持,定义了对缓冲区的通用操作,如设置缓冲区、从缓冲区中读取数据,写入数据等操作
- 为了便于程序数据的输入输出,C++预定义了几个标准输入输出流对象
- cout:ostream cout,与标准输出设备关联
- cin: istream cin,与表示输入设备关联
(非缓冲方式) - cerr: ostream cerr,与标准错误设备关联(非缓冲方式)
- clog: ostream clog,与标准错误设备关联(缓冲方式)
istream流
- istream类定义了许多用于从流中提取数据和操作文件的成员函数,并对流的析取运算符>>进行了重载,实现
了对内置数据量的输入功能。其中常用的几个成员函数是get、getline、read 、ignore.
get(char_tyoe &ch);//从输入流中提取一个字符(包括空白字符)
getline(char_type*s,std::streamsize count,char_type delim);//一次性读取一行字符
read(char_type *s,std::streamsize count);//从输入流一次性读取count个字符
ignore(std::streamsize count = 1,int_type delim = Traits::eof());//从输入流中读取字符并丢弃
cin.getline(a,100);
ostream流
- stream类提供了许多用于数据输出的成员函数,并通过流的输出<<重载,实现了对内置数据类型的输出功能
- 常用的成员函数:
- put
- write
- flush
put(char_type ch);//插入一个字符到输出流
write(const char_type* s,std::streamsize count);//插入一个字符序列到输出流中
flush();//刷新输出流
string流
- istringstream
- ostringstream
//头文件 <sstream>
int i =123;
double d = 56.78;
char s[] = "hello";
ostringstream oss;
oss << i << ' ' << ' ' << d << ' ' << s;
istringstream iss;
iss >> i >> d >> s;
文件流
- ofstream
- ifstream
//头文件 <fstream>
int i =123;
double d = 56.78;
char s[] = "hello";
ofstream ofs("a.txt");
ofs << i << d << s <<endl;
ofs.close();
ifstream ifs("a.txt");
ifs >> i >> d >> s;
模板
-
C++提供两种模板机制
- 函数模板
- 类模板
-
语法
-
template <typename T>
-
template 声明创建模板
-
typename 表示其后面的符号是一种类型,也可以用class
-
T 通用数据类型,名称看可以替换,通常为大小写字母
-
函数模板
template <typename type>
ret-type func-name(parameter list)
{
// 函数的主体
}
类模板
template <class type>
class class-name
{
}
void class-name<T>::func-name (T list)
STL
- STL就是标准模板库,它提供了模板化的通用类和通用函数。
- 核心内容:
- 容器
- 顺序容器
- 常被称为序列容器,他是将相同类型对象的有限集按顺序组织在一起的容器,用来表示线性数据解结构
- 向量(vector)
- 链表(list)
- 双端队列(deque)
- 关联容器
- 关联容器是非线性容器,是用来根据键(key)进行快速存储、检索数据的容器。这类容器可以存储值的集合或键值对
- 集合(set)
- 多重集合(multiset)
- 映射(map)
- 多重映射(multimap)
- 容器适配器
- 它们实际是受限制访问的顺序容器类型
- 堆栈(stack)
- 队列(queue)
- 迭代器
- 算法
- 容器
- 容器是用来存储其他对象的,容器是容器类的实例,而容器类使用类模板实现的,适用于各种数据类型。
STL中的十大容器
STL容器名 | 头文件 | 说明 |
---|---|---|
vector | 向量,从后面快速插入和删除,直接访问任何元素 | |
list | 双向链表 | |
deque | 双端队列 | |
set | 元素不重复的集合 | |
multiset | 元素可重复的集合 | |
stack | 堆栈,先进后出 | |
map | 一个键只对应一个值得映射 | |
mutimap | 一个键可对应多个的映射 | |
queue | 队列,先进先出 | |
priority_queue | 优先级队列 |
所有容器都具有的成员函数
成员函数 | 说明 |
---|---|
默认构造函数 | 对容器进行默认初始化的构造函数,常有多个,用于提供不同的容器初始化方法 |
拷贝构造函数 | 用于将容器初始化为同类型的现有容器的副本 |
析构函数 | 执行容器销毁时清理工作 |
empty() | 判断容器是否为空,为空返回true |
max_size() | 返回容器的最大容量 |
size | 返回容器中当前元素的个数 |
operator= | 将一个容器赋给另一个同类容器 |
operator< | 如果第一个容器小于第二个容器,返回true |
operator<= | 如果第一个容器小于等于第二个容器,返回true |
operator>= | 如果第一个容器大于第二个容器,返回true |
operator> | 如果第一个容器大于等于第二个容器,返回true |
swap | 交换两个容器中的元素 |
vector容器
- vector实现的是一个动态数组,可以进行元素的插入和删除
- 在此过程中,vector 会动态调整所占用的内存空间,整个过程无需人工干预
//头文件 <vector> 并位于std命名空间中
vector<T> //T代表元素的类型
- 创建容器的方式
//创建空的向量容器
vector<double> values;
values.reserve(20);
//指定初始值以及元素个数
vector<int> primes {2,3,5,7,11,17,19};
//创建vector容器时,指定元素个数
vector<double> values(20);//有20个元素,他们的默认初始值为0
vector<double> values(20,1.0);//有20个元素,他们默认的初始值为1.0
//通过存储元素类型相同的其他,vector 容器,创建新的vector容器
vector<char> value1(5,'c');
vector<char> value2(value1);
- vector容器包含的成员函数
成员函数 | 函数功能 |
---|---|
begin() | 返回指向容器中第一个元素的迭代器 |
end() | 返回指向容器最后一个元素所在位置后一个位置的迭代器,通常和 begin( 结合使用。 |
rbegin() | 返回指向最后一个元素的迭代器 |
rend() | 返回指向第一个元素所在位置前一个位置的迭代器 |
cbegin() | 和 begin0功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 |
cend() | 和 end0 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元养 |
cebegin() | 和 rbegin0功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素 |
crend() | 和 rend0 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素 |
size | 返回实际元素个数 |
max_size | 返回元素个数的最大值。这通常是一个很大的值,一般是 232-1,所以我们很少会用到这个函数 |
resize() | 改变实际元素的个数 |
capacity() | 返回当前容量。 |
empty() | 判断容器中是否有元素,若无元素,则返回 rue; 反之,返回 false |
reserve() | 增加容器的容量 |
shrink_to_fit() | 将内存减少到等于当前元素实际所使用的大小 |
operator[] | 重载了 [] 运算符,可以向访问数组中元素那样,通过下标即可访问甚至修改vector 容器中的元素 |
at() | 使用经过边界检查的索引访问元素 |
front() | 返回第一个元素的引用。 |
back() | 返回最后一个元素的引用。 |
data() | 返回指向容器中第一个元素的指针 |
assign() | 用新元素替换原有内容。 |
push_back() | 在序列的尾部添加一个元素。 |
pop_back() | 移出序列尾部的元素 |
insert() | 在指定的位置插入一个或多个元素 |
erase() | 移出一个元素或一段元素 |
clear() | 移出所有的元素,容器大小变为 0 |
swap() | 交换两个容器的所有元素 |
emplace() | 在指定的位置直接生成一个元素 |
emplace_back() | 在序列尾部生成一个元素 |
list容器
- list容器,又称双向链表容器,即该容器的底层是以双向链表的形式实现的
- list容器中的元素可以分散存储在内存空间里,而不是必须存储在一整块连续的内存空间中
//头文件 <list> 命名空间std
#include <list>
using namespace std;
- list容器的创建
list<int> values;//没有任何元素
list<int> values(10);//包含10个元素每个元素的值都为相应类型默认值(int类型默认值为0)
list<int> values(10,5);//包含10个元素并且都为5
list<int> value2(values);//和values元素个数,内容相同
//拷贝普通数组,创建list容器
int a[] = {1,2,3,4,5};
std::list<int>values(a,a+5);//values中5个元素
stack容器
- STL提供了3 种容器适配器
- stack 栈适配器
- queue 队列适配器
- priority_queue 优先权队列适配器
- 容器适配器本质上还是容器,只不过此容器模板类的实现,利用了大量其它基础容器模板类中已经写好的成员函数
stack<int> s;
s.push(10);
s.pop();
s.top();
s.empty();
迭代器
- 迭代器(iterator)是一个对象,常用它来遍历容器
- 不管容器是否直接提供了访问其对象的方法,通过迭代器都能够访问该容器中的元素,一次访问一个元素
- 迭代器是STL的核心,它定义了哪些算法在哪些容器中可以使用,把算法和容器连接起来,使算法、容器和迭代器能够协同工作,实现强大的程序功能。
- 若某个容器要使用迭代器,它就必须定义迭代器。
- 定义迭代器时,必须指定迭代器所使用的容器类型
list<int>::iterator L1;//迭代器声明
- 完成该定义后,STL会自动将此迭代器转换成链表所需要的双向迭代罪,该迭代器可以用于int型的list
operator*//返回当前位置的元素值
operator++//将迭代器前进到下一个元素的位置
operator--//将迭代器后退到前一个元素的位置
operator==或operator!=//判定两个迭代器是否指向同一个位置
operator=//为迭代器赋值
begin()//指向容器起点位置
end()//指向容器的结束点,结束点在最后一个元素的位置
rbegin()//指向按反方向顺序的第一个元素位置
rend()//指向按反方向顺序的最后一个元素的位置
关联式容器
- STL关联容器包括
- 集合
- set
- multiset
- 集合类multiset和set提供了控制数字(包括字符及串)集合的操作,集合中的数字称为关键字,不需要有另一个值与关键字相关联
- set和multiset会根据特定的排序准则,自动将元素排序,两者提供的操作方法基本相同,只是multiset允许元素重复而set不允许重复
- 映射
- map
- multimap
- 它们通过关键字存储和查找元素。在每种关联容器中,关键字按顺序排列,容器遍历就可以顺序进行
集合容器
- 其中op可以是less<>或greater<>之一,应用时须在<>中写上类型,如greater。less指定排序方式为从小到大,greater指定排序方式为从大到小,默认排序方式为less
int a1[] = {-2,3,457,2,46,823,7,9};
set<int,greater<int> > set1(a1,a1+10);
set<int,greater<int> >::iterator it;//迭代器
multiset<string> set2(a2,a2+10);
映射容器
-
map和multimap提供了操作<键,值>对的方法,它们存储一对对象,即键对象和值对象
-
键对象是用于查找过程中的键,值是与键对应的附加数据
-
若键为单词,对应的值是表示该单词在文档中出现此数的数字,map就成了统计单词在文本中出现次数的频数表
-
若键为单词,值是单词出现的页号链表,用multimap实现这样的键值对象就是可以构造单词索引表。
-
map中的元素不允许重复,而multimap中的元素是可以重复的
-
insert插入到map/multimap和set/multiset的元素是有区别的。插入到set/multiset中的元素是单独的键,而插入到map/multimap中的元素是<键,值>构成的一对数据,这对数据是一个不可分割的整体
-
map/multimap映射的元素是由<键,值>对构成的,且同一个键可以对应多个不同的值,可以通过相关映射的迭代器访问他们的元素
-
map/multimap类型的迭代器提供了两个数据成员:一个是first,用于访问键,一个是second用于访问值。
-
此外map类型的映射可以用键作为数组下标,访问该键所对应的值,但multimap类型的映射不允许用数组下标的方式访问其中的元素
//头文件 <map>
map<string,double> sa1;
map<string,double>::iterator it;
sa1.insert(make_pair(name[i],salary[i]));
cout << it->first << it->second << endl;
multimap<string,string> dict;
multimap<string,string>::iterator dict;
dict.insert(make_pair(eng[i],chr[i]));//插入键值对
迭代器和指针
-
指针是一种变量
,其值为另一种类型的对象在计算机内存中的地址 -
迭代器是一种对象
,它能够遍历并操作某种数据结构(如数组、列表、集合等)中的元素 -
解引用符与箭头符在指针与迭代器上的用法区别
- 对于指针,
操作符*用于获取指针指向的对象,而操作符->用于访问指针指向的对象的成员
- 对于迭代器,
操作符*同样用于获取迭代器指向的对象,而操作符->则用于访问迭代器指向的对象的成员
(只有在该对象是类或结构时才能这样做)
- 对于指针,
find和count算法
- find用于查找指定数据在某个区间中是否存在,该函数返回等于指定值的第一个元素位置,如果没找到就返回最后元素位置
- count用于统计某个值在指定区间出现的次数
//需要包含算法头文件 <algorithm>
int a1[] = {1,2,3,4,5,6,7,8,9,10};
int *ptr = find(a1,a1+10,8);
cout << "8出现的位置" << ptr-a1 << endl;
it = find(a1.begin(),a1.end(),8);
int n1 = count(a1,a1+10,5);
cout << "5出现了" << n1 << "次" << endl;
int n2 = count(a1.begin(),a1.end(),8);
search算法
- find算法从一个容器中查找指定的值,search算法则是从一个容器查找由另一个容器所指定的顺序值
- search将在[beg1,end1)区间查找有无与[beg2,end2)相同的子区间,如果找到就返回[beg1, end1)内第1个相同元素的位置,如果没有找到返回end1
search用法如下所
int a1[] = {1,2,3,4,5,6,7,8,9};
int a2[] = {7,8,9};
int *ptr = search(a1,a1+9,a2,a2+3);
if(ptr == a1+9)
{
cout << "not" <<endl;
}
else
{
cout << "math" <<ptr - a1 <<endl;
}
list<int> L;
vector<int> V;
for(int i=0;i<9;i++)
{
L.push_back(a1[i]);
}
for(int i=0;i<3;i++)
{
V.push_back(a2[i]);
}
list<int>::iterator pos;
pos = search(L.begin(),L.end(),V.begin(),V.end());
cout << distance(L.begin(),pos);
merge算法
-
merge可对两个容器进行合并,将结果存放在第3个容器中
-
merge将[beg1,end1)与ibeg2, end2)区间合并,把结果存放在dest容器中,如果参与合并的两个容器中的元素是有序的,则合并的结果也是有序的
-
它能够把两个list类型的链表合并在一起。同样地,如果合并前的list链表也提供了一个merge成员函数,链表是有序的,则合并后的链表仍然有序
int a1[] = {1,2,3,4,5,6,7,8,9};
int a2[] = {7,8,9,10};
int a3[13] = {0};
merge(a1,a1+9,a2,a2+4,a3);
list<int> L1;
list<int> L2;
for(int i=0;i<9;i++)
{
L1.push_back(a1[i]);
}
for(int i=0;i<4;i++)
{
L2.push_back(a2[i]);
}
L1.merge(L2);
list<int>::iterator it;
for(it=L1.begin();it!=L1.end();it++)
{
cout << *it <<"\t";
}
cout << endl;
sort排序算法
- sort可对指定容器区间内的元素进行排序,默认排序的方式为从小到大,其用法入下:
int a2[] = {10,23,76465,27,1,-12,74};
vector<int>V;
vector<int>::iterator it;
for(int i=0;i<7;i++)
{
V.push_back(a2[i]);
}
sort(V.begin(),V.end());
for(it=L1.begin();it!=L1.end();it++)
{
cout << *it <<"\t";
}