tips
编译和调试的区别
编译(compile):依赖于编译器,将源码转化成目标文件 如.obj
调试:让程序在系统中运行之前查错和改错,找出并修正潜在的错误
linux c++的四个处理过程
- 预处理
将所有#include头文件以及宏定义替换成其真正的内容 - 编译
将经过预处理之后的程序转换成特定汇编代码的过程 - 汇编
将上一步的汇编代码转换成机器代码,这一步产生的文件叫做目标文件 - 链接
将多个目标文件以及所需的库文件(.so)链接成最终的可执行文件
数据类型
4种转换较为规范的运算符
reinterpret_cast<type-name>(expression)
- reinterpret_cast 可以将指针类型转换为足以存储指针表示的整型
- 不能将函数指针转换为数据指针
枚举类型 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[]释放占用的内存
int size;
cin >>size;
int *arr = new int [size];
……
delete[] arr;
数组区间的函数
指定元素区间 可以通过传递两个指针完成:一个指针标识数组的开头,另一个指针标识数组的尾部
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)
结构体
struct typename {
char name[10];
int ages;
}
- new 创建动态结构
inflat *ps = new inlat;
使用完成后要用delete释放内存 delete ps;
ps -> price 也是指向结构的price成员
ps 是指向结构的指针,则*ps就是指向的值–结构本身
(*ps)是一种结构 (*ps).name 是该结构的name成员
函数
函数原型
原型可以帮助编译器完成许多工作,降低程序出错的机率,具体有一下几点:
- 编译器正确处理函数返回值
- 编译器检查使用的参数数目是否正确
- 编译器检查使用参数类型是否正确
#define的定义
(文本替换)
define 是宏定义,程序在预处理阶段将用define定义的内容进行替换。程序在运行时,常量表中并没有用define定义的常量,系统不会为它分配内存
-
#define定义一个标识符来表示一个常量。
特点:定义的标识符不占内存,只是一个临时的符号,预编译后这个符号就不存在了- 宏展开是在预处理阶段完成的,这个阶段把替换文本只看做一个字符串,并不会有任何的计算发生。
- 宏其实就是简单的文本替换。
定义标识符的一般形式:
#define 标识符 常量 //注意,最后没有分量
- 在大规模的开发过程中,特别是跨平台和系统的软件里,define最重要的的功能就是条件编译
#ifdef WINDOWS
……
#endif
#ifdef LINUX
……
#endif
可以在编译的时候通过#define设置编译环境。
函数指针
使用函数指针的三个步骤:
- 声明函数指针
- 让函数指针指向函数地址
- 通过函数指针调用函数
声明函数指针
double pam(int) //函数原型
double (*f)(int) //函数指针
pam 替换为了(*f).由于pam是函数名 即(*f)也是函数名, f 是函数指针
函数指针调用函数
//函数指针调用函数
f = pam;
double y = pam(2);
double x = (*f)(5);
#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;
}
引用&
- 形参需要修改实参时
- 当实参较大时,如(数据 结构体)传递引用 系统不会生成临时变量 较小的内存消耗
形参实参的三种传值方式
- 按值传递
- 按地址传递 形参改变实参
- 按引用传递
#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;
}
模板
函数模板
每声明一个模板函数 都必须有一下两行声明;
template<typename T>
void swap(T a,T b){
T temp;
temp = a;
a = b;
b = temp;
}
建立一个模板,关键字template和typename是必需的
- 显示调用
swap<int>(a,b);
- 隐式调用
swap(a,b);
重载模板
需要多个对不同类型使用同一种算法的函数时,可以使用模板。可以像重载常规函数定义那样重载模板定义。
#include<iostream>
const int size = 8;
template<typename T>
void swap(T& a, T& b) {
T temp;
temp = a;
a = b;
b = temp;
}
template<typename T>
void swap(T* a, T* b, int n) {
T temp;
for (T i = 0; i < n; i++) {
temp = a[i];
a[i] = b[i];
b[i] = temp;
}
}
void show(int a[]) {
for (int i=0;i<size;i++)
{
std::cout << a[i];
}
std::cout << std::endl;
}
int main() {
int n = 10;
int m = 20;
swap(n, m);
std::cout << "n的值是" << n << std::endl;
std::cout << "m的值是" << m << std::endl;
int a[size] = { 1,2,3,4,5,6,7,8 };
int b[size] = { 9,8,7,6,5,4,3,2 };
swap( a, b,size);
show(a);
}
类模板
建立一个通用类,类中的成员 数据类型可以不做具体指定
template<typename T>
类
#include <iostream>
using namespace std;
template<typename T1, typename T2>
class Person {
public:
Person(T1 name, T2 age);
T1 m_name;
T2 m_age;
};
template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->m_age = age;
this->m_name = name;
}
void test()
{
Person<string, int> p1 = Person<string, int>("zhangsan", 18); //无自动推导 要在积极手动加入
Person<string, int> p2("lisi", 20);
cout << p1.m_age << p1.m_name << endl;
cout << p2.m_age << p2.m_name << endl;
}
int main() {
test();
}
类模板与函数模板的区别
- 类模板没有自动类型推导的使用方式
void test()
{
Person<string, int> p1 = Person<string,int>("zhangsan", 18);
Person<string, int> p2("lisi", 10);
}
- 类模板在模板参数列表可以有多个默认参数
template<typename T1,typename T2=int> //默认类型
class Person {
public:
Person(T1 name, T2 age);
T1 m_name;
T2 m_age;
void showmess();
};
//调用
Person<string> p1("zhangsan",19);
模板函数中成员函数的创建时机
类模板中的成员函数 并不是一开始就创建的,而是在模板调用时再生成
类模板对象做函数参数
- 指定传入类型
//指定传入类型
void showperson1(Person<string,int>&p) {
cout << "姓名1:" << p.m_name << "年龄1 :" << p.m_age << endl;
}
void test1() {
Person<string, int > p1("zhangsan", 18);
showperson1(p1);
}
模板类分文件编写 可以将.h和.cpp中的内容写到一起,将后缀名改成.hpp
类模板与友元
实现步骤
- 先在类定义的前面声明每个函数模板
template<typename T1, typename T2>
class Person;
template <typename T1, typename T2>
void show(Person<T1, T2> p)
{
std::cout << p.m_age << std::endl;
}
- 在函数中再次将模板声明为友元。同时在声明中函数名后加<> 这是模板的具体化
template<typename T1,typename T2>
class Person
{
// 加一个空模板的参数列表
friend void show<>(Person<T1,T2> p);
public:
Person(T1 name,T2 age);
private:
T1 m_name;
T2 m_age;
};
void test() {
Person<std::string, int> p1("zhangsan", 16);
show(p1);
}
类
对象和类
指定类设计的第一步是提供类声明。类声明类似结构声明,包括数据成员和函数成员。
- 提供类声明(数据成员和函数成员)
- 私有部分声明只能通过成员函数进行访问
class person{
private:
//数据项目通常放在私有部分
char name[10];
int ages;
public:
//组成类接口的成员函数放在公有部分
void eating();
void sleep();
}
公有部分的内容构成了设计的抽象部分————公有接口
实现类成员函数
-
成员函数的函数头使用(::)来指出函数所属的类
-
类方法可以访问类的private组件
:: 作用域解析符
void person ::sleep( );
类成员函数可通过类来调用,需要使用成员运算符.
#include<iostream>
#include<string>
//std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield);
class Stock //定义Stock类
{
public:
void acquire(const std::string& co, long n, double pr);
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show();
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_tot() { total_val = share_val * shares; };
};
void Stock::acquire(const std::string& co, long n, double pr) //
{
company = co;
if (n < 0) {
std::cout << "no shares";
shares = 0;
}
else
shares = n;
share_val = pr;
set_tot();
}
void Stock::show() {
using namespace std;
cout << "company s shares is " << shares << endl;
cout << "company s shares is " << share_val << endl;
cout << "company s shares is " << total_val << endl;
}
int main() {
Stock tencent, ali; //创建两个Stock对象
tencent.acquire("baidu", 20, 12.50);
tencent.show();
return 0;
}
static关键字的用法
- 函数的变量存放在栈区,函数调用完毕分配的空间就会释放。如若想要持续使用函数的变量,可以引入关键字static,函数中的变量可以持续到程序执行完毕。(类比于全局变量,全局变量的缺点就是破坏了此变量的访问范围);
- 在C++中,需要一个数据对象为整个类而不是某个对象服务,同时又不破坏类的封装性,可以使用static修饰。
在内存四区中,静态变量存储在全区局,程序结束运行后才会释放。
static的作用
- 延长了变量的作用时间,一直到程序结束后才释放;
- static修饰的全局变量,只能在该文件中访问,即使是extern外部声明也不可以;
- statci修饰的函数,只能在该文件中调用;
- 静态变量相对于全局变量跟为安全。
静态变量不能被其他文件所用,其他文件可以定义相同的变量名称,不会冲突
全局变量默认是有外部链接性的,作用域是整个工程,其他文件想要访问,可以使用extern
在C++中,静态成员是属于整个类的而不是某个对象。静态成员可以通过双冒号来使用<类名>::<静态成员名>
1. 类的对象可以使用静态函数和非静态函数。
2. 静态函数中不能引用非静态成员变量,非静态函数可以引用静态变量
3. **类的静态成员变量在使用前必须先初始化** `int student::n = 0;`
#include<iostream>
class student {
public:
static void show1(int n);
void show2();
private:
static int n; //声明静态局部变量
double s;
};
int student::n = 0; //定义并初始化静态成员变量
void student::show1(int m) {
student::n = m;
std::cout << "static的作用" << std::endl;
std::cout << "static变量n的值: " << std::endl;
}
void student::show2() {
std::cout << "showtime" << std::endl;
}
int main() {
student li;
student::show1(5);
/*student::n = 10;*/
li.show1(3); //对象调用静态成员函数
li.show2();
}
类构造函数
专门由于构造新对象、将值赋给它们的数据成员
名称与类名相同
Stock类的构造函数Stock()
声明类构造函数
Stock(const string &co,long n = 0,double pr = 0.0) //声明类构造函数
定义构造函数
//定义构造函数
Stock ::Stock(const string & co, long n,double pr){
}
与普通函数不同的是,程序声明对象时,将自动调用构造函数
使用构造函数
- 显性调用
Stock stock1 = Stock("zkhx",3,2.1);
- 隐性调用
Stock stock1("zkhx",3,2.1);
创建对象的同时进行了初始化
stock1.show();
构造函数用来创建对象,而不能通过对象来调用
析构函数
~DataTimerTest()
函数前面加~表示析构函数
就像对象被创建时程序将调用构造函数一样,当对象结束生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数,释放内存
(每个类只能有一个析构函数)
//析构函数
data_queue::~data_queue() {
if (m_minBufferData) {
delete m_minBufferData;
m_minBufferData = NULL;
}
if (m_maxBufferData) {
delete m_maxBufferData;
m_maxBufferData = NULL;
}
}
如函数使用了指针变量,析构函数中加入delete
delete释放指针原本所指的内存,指针被delete后,如果不置为null,指针就会成为野指针,会在内存乱指一通。,再次调用可能会导致系统崩溃。
对一个非空指针delete后,若没有赋null,再次delete是不可行的
第一次delete指针后,指针的地址并不是空的,同一块内存释放两次会导致崩溃。
总结: delete一个空指针是合法的,为了防止多次delete指针导致程序崩溃,需要养成良好的置空(null)习惯
this指针
-
解决名称冲突
this指针指向 被调用的成员函数 所属的对象
class Person{ public: Person(int age){ this->age = age; } Person& addage(Person &p){ this->age +=p.age; return *this; //this 指向p2的指针,而*this指向的就是p2这个对象的本体 } int age; } void test(){ Person p1(18); cout <<p1.age<<std::endl; }
-
返回对象本身用
*this
链式编程思想
Person& Person::addage(Person& p)
{
this->age += p.age;
return *this;
}
p2.addage(p1).addage(p2) //链式编程思想
如果不用引用的方式返回,相当于返回与p2不同的另一个Person(只是age都是20),那么后续的加年龄操作与p2就没有关系了;
返回是值的话,相当于创建了一个新的对象,同时调用了拷贝构造函数
对象数组
通常要创建同一个类的多个对象。声明对象数组的方法与声明标准类型数组相同
stock mystuff[4]; //创建一个对象数组
每个元素(mystuff[0],mystuff[1])都是stock的对象
mystuff[0].update();
可以用构造函数初始化数组元素。在这种情况下,必须为每个元素调用构造函数:
const int STK =4;
Stock stock[STK] = {
Stock("NanoSmart",12.3,30);
Stock("zkhx",12,4);
Stock();
};
运算符重载
对已有的运算符重新进行定义,赋予另一种功能,以适应不同的数据类型
加号运算符重载
实现自定义的加法运算
#include<iostream>
using namespace std;
class Person
{
public:
//1.成员函数重载+号
Person operator+(Person& p) {
Person temp;
temp.m_a = this->m_a + p.m_a; //this只能用于非静态成员函数
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 + p2.m_a;
temp.m_b = p1.m_b + p2.m_b;
return temp;
}
Person operator+(Person& p1, int n) { //运算符重载外加函数重载
Person temp;
temp.m_a = p1.m_a + n;
temp.m_b = p1.m_b + n;
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;
cout << "p3.m_a的值 " << p3.m_a << endl;
cout << "p3.m_b的值 " << p3.m_b << endl;
//运算符重载 也可以发生函数重载
Person p4 = p1 + 15; // Person + int
cout << "p4.m_a的值 " << p4.m_a << endl;
cout << "p4.m_b的值 " << p4.m_b << endl;
}
int main() {
test01();
return 0;
}
全局函数可以改变运算顺序 成员函数不行
总结1:对于内置的数据类型的表达式的运算符是不可修改的
总结2:不要滥用运算符重载
下面的运算符只能通过成员函数进行重载
= 赋值运算符
( ) 函数调用运算符
[ ] 下标运算符
-> 通过指针访问类成员的运算符
左移运算符重载
实现自定义的输出
- <<的第一种重载方式
void operator<<(ostream& cout, Person& p);
cout << p;
这种重载方式只能加载左右两个重载对象
- <<的第二种重载方式
ostream& operator(ostream& cout,Person& p){
cout << x << y;
return cout;
}
<<运算符要求左边有一个ostrem对象。所以表达式cout<<p
满足这种要求。然而,表达式cout<<x<<y
,cout<<x
位于<<y
的左侧,所以输出语句也要求该表达式是一个ostream类型的对象。
因此,ostream类将operator<<()
函数实现为返回一个指向ostream对象的引用
#include<iostream>
using namespace std;
class Person
{
friend void operator<<(ostream& cout, Person& p); //友元函数声明
public:
Person(int a, int b) { //有参构造函数
m_a = a;
m_b = b;
}
//成员函数重载左移运算符
private:
int m_a;
int m_b;
};
void operator<<(ostream& cout, Person& p) {
cout << "p.m_a" << p.m_b ;
}
ostream& operator<<(ostream& cout, Person& p) { //链式编程
cout << "p.m_b" << p.m_b << "hello world" << endl; //如若想在cout后链接其他输出 需要返回ostream类对象,并在后面加上引用
return cout;
}
void test() {
/*Person p(10,20);*/ //隐式使用构造函数
Person p = Person(10, 20); // 显式使用构造函数
cout << p;
operator(cout,p);
}
int main() {
test();
}
友元函数
全局函数做友元
成员函数不能改变运算顺序 全局函数可以 但非成员函数不能直接访问类的私有数据。这时候就要引入一个特殊的函数 友元函数
- 创建友元函数
创建友元函数的第一步是将其原型放在类声明中,并在前面加上一个friend
friend Person operator*(double m ,Person &p2)
虽然operator放在类声明中,但它不是成员函数 不能使用成员符号调用
operator不是成员函数 但是它有访问私有变量
- 定义函数
友元函数不是成员函数 不能使用类::限定符。定义中不用加friend
class Person {
public:
friend void operator*(double n, Person& p1);
private:
int m_a;
int m_b;
};
void operator*(double n, Person& p1) {
p1.m_a = 10;
p1.m_b = 23;
Person total;
total.m_a = n * p1.m_a;
total.m_b = n * p1.m_b;
std::cout << total.m_a << std::endl;
}
类做友元
在想要访问的类中加入友元friend类
#include<iostream>
using namespace std;
class Building;
class Goodgay
{
public:
Goodgay();
void visit();
private:
Building* building;
};
class Building {
friend class Goodgay;
public:
Building();
string m_livingroom ;
private:
string m_bedroom;
};
Goodgay::Goodgay() {
building = new Building;
}
Building::Building() {
this->m_bedroom = "卧室";
this->m_livingroom = "客厅";
}
void Goodgay::visit() {
cout << "好朋友正在访问" << building->m_livingroom << endl;
cout << "好朋友正在访问" << building->m_bedroom << endl;
}
int main() {
Goodgay li;
li.visit();
return 0;
}
成员函数做友元
friend void Goodgay::visit();
class Building {
public:
friend void Goodgay::visit(); //将成员函数写在要访问的类中
Building();
string m_livingroom;
private:
string m_bedroom;
};
类继承
下级别的成员有上一级的共性,还有自己的特性
减少重复代码
语法: class 子类: 继承方式 父类
继承方式
- 公共继承
- 保护继承
- 私有继承
三种继承法是,父类的私有部分,任何一种方式都不能继承。
保护继承将父类的公有的,保护的都转为子类保护的,私有继承将父类公有的,保护的都转为子类保护的
父类:
class Pro{
public:
int a;
protected:
int b;
private:
int c;
}
公共继承: 私有继承: 保护继承:
class A :public Pro{ class B:private Pro{ class C:protected Pro{
public: private: protected:
int a; int a; int a;
protected: int b; int b;
int b; 不可访问: 不可访问:
不可访问: int c; int c;
int c; }; };
};
在父类中所有非静态成员属性都会被子类继承下去,父类私有成员属性,是被编译器给隐藏了,因此访问不到,但是确实被继承了。
继承中,父类子类构造和析构的顺序
在继承的过程中,子类继承父类,他们的顺序可以类比于栈的过程
先构造父类 在构造子类 接在析构子类 再析构父类
同名成员处理
Son s;
std::cout << "使用子类同名变量" << s.m_a << std::endl;
std::cout <<"子类对象使用父类同名变量"<<s.Base::m_a << std::endl;
子类在继承父类时有同名变量
如果通过子类对象访问 父类同名成员,需要加作用域
总结:
- 子类对象可以直接访问父类同名成员
- 子类对象加作用域可以访问父类同名成员
- 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问父类同名函数
同名静态函数与普通函数访问的方式一致,只是要加上static的属性,有两种访问方式1.按类名访问2.按对象访问
多重继承为防止二义性 可以用::作用域
菱形继承
虚基类
#include<iostream>
using namespace std;
class Animal //虚基类
{
public:
int m_age;
};
class Sheep : virtual public Animal {}; //virtual继承
class Tuo:virtual public Animal{};
class Sheeptuo:public Sheep,public Tuo{};
void test01() {
Sheeptuo st;
st.Sheep::m_age = 100;
st.Tuo::m_age = 200;
cout << st.Sheep::m_age << endl;
cout << st.Tuo::m_age << endl;
cout << st.m_age << endl;
}
int main() {
test01();
return 0;
}
菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费,可以使用virtual加以避免
多态
- 静态多态:函数重载 运算符重载
- 动态多态:派生类和虚函数实现运行时多态
区别:
静态 编译阶段确定函数地址
动态 运行阶段确定函数地址
动态多态满足的条件
- 子类继承父类
- 子类重写父类(virtual)虚函数
重写:函数返回值 函数名 参数列表 完全一致
动态多态的使用
父类的指针或引用指向子类的对象
class Animal
{
public:
virtual void speak() {
cout << "动物说话" << endl;
}
};
class Cat :public Animal {
public:
void speak() {
cout << "小猫叫" << endl;
}
};
//动态多态的使用
//父类的指针或引用 执行类对象
1. 方法一 父类的引用指向子类对象
void dospeak(Animal& animal) //Animal & animal = cat;
{
animal.speak();
}
void test() {
Cat cat;
dospeak(cat);
2. 方法二 父类的指针指向子类对象
void dospeak(Animal* animal) //Animal * animal = new Cat;
{
animal->speak();
}
void test() {
dospeak(new Cat);
什么类型的指针都占用四个字节
加了个virtual 等于加了个指针vfptr(virtual function pointer)
非静态成员函数不属于类的对象上
纯虚函数
多态中,通常父类中的虚函数的实现毫无意义,主要都是调用子类重写,可以把这类函数改成纯虚函数
virtual 返回值类型 函数名(参数列表) = 0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类的特点
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include <iostream>
using namespace std;
class Base {
public:
virtual void funct() = 0; //纯虚函数
};
class Son1 :public Base {
public:
void funct() {
cout << "hello " << endl;
}
};
class Son2 :public Base {
public:
void fi() { //没有重写纯虚函数 该类不能实例化
cout << "world" << endl;
}
};
void test() {
// Son2 ji; // !!报错
Son1* ji = new Son1;
ji->funct();
}
String类
string是一个类,类内部封装了char*,管理这个字符串,是一个char*型的容器
find delete insert
string构造函数
string赋值操作
#include<iostream>
#include<string>
using namespace std;
void test01() {
std::string str;
str = "hello world";
std::cout << str << std::endl;
std::string str1("hallo");
std::string str2;
str2 = 'a';
std::cout << str2 << std::endl;
string str5;
str5.assign("helloworld");
cout << str5 << endl;
string str6;
str6.assign("helloworld", 5);
cout << str6 << endl;
string str7;
str7.assign(str6);
cout << str7 << endl;
string str8;
str8.assign(20, 'w');
cout << str8 << endl;
}
string的赋值方式很多,operator=这种方式是比较实用的
字符串拼接
string +=str1
string查找和替换
find和rfind replace
void test01() {
string str1 = "abcdefg";
int n =str1.find('cf'); //从左往右找 查到了 返回下表索引,没找到 返回-1
cout << n << endl;
int j = str1.rfind("de"); //从右往左找
cout << j << endl;
}
void test02() {
string strl = "abcdefghi";
strl.replace(1, 3, "233"); //a索引1到3 替换成233
cout << strl << endl;
}
字符串的比较
string字符存取
char &operator[] (int n);
通过[]方式取字符
char & at(int n)
通过at方法取字符
void test() {
string str = "hello";
//通过[]访问单个字符
for (int i = 0; i < str.size(); i++) {
cout << str[i];
}
cout << endl;
// 通过at访问单个字符
for (int i=0;i<str.size();i++)
{
cout << str.at(i);
}
cout << endl;
str[0] = 'x'; //使用[]修改字符串内容
cout << str;
}
string字符串的插入和删除
插入和删除起始下标都是从0开始的
void test() {
string str = "hello";
//插入
str.insert(3, "liu");
cout << str << endl;
//删除
str.erase(3);
cout << str << endl;
}
string子串
substr(1,3)
void test1() {
string email = "zhangsan@sina.com";
int n = email.find('@');
cout << email.substr(0, n) << endl;
}
STL模板
vector容器
与数据相似,也成为单端数组(类比于栈)
vector与普通的数组的区别
数组时静态空间,而vector可以动态扩展
动态扩展
并不是在原有的基础上进行拓展,而是找一个更大的空间,将原数据拷贝到新空间,释放原空间
vector迭代器 支持随机访问的迭代器
vector四种构造方法
- 默认构造
- 区间构造
- 拷贝构造
- n个元素构造
#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的增删查
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结构类型 要先指定类型规则
语法
//指定排序规则 如果年龄相等 按照体重排序
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;
}
}
#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属于关联容器,底层结构是二叉树实现
区别
- set不允许容器有重复的元素
- 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值
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;
map<int, int>::iterator it = m.find(9);
cout << (*it).first <<it->second<<endl;
cout() 0or1
使用仿函数改变map的默认排序 由大到小
仿函数 传入的时候传入一个数据类型,在类中重载了()函数
重载()
class Mycompare {
public:
bool operator()(int v1, int v2) const 自定义排序规则
{
return v1 > v2;
}
};
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);
}
函数对象
重载函数调用操作符的类,其对象成为函数对象
对()重载的函数 也叫仿函数
#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++");
}
内建仿函数
算术仿函数
#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>
自定义
class Mycompare {
public:
bool operator()(int v1, int v2) const 自定义排序规则
{
return v1 > v2;
}
};
STL-常用算法
算法主要是由头文件 最大的一个,涉及比较、交换、查找、遍历仿函数
常用遍历算法
for_each(iterator_beg;iterator_end;函数 仿函数);
#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_函数)
搬运的目标容器必须提前开辟空间,否则无法正常搬运
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());
}
常用的查找算法
find
find(iterator.beg(),iter.end(),value)
find 可以在容器中找指定的元素,返回值是迭代器
自定义类型 需要写仿函数 对"=="就行重载
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;
}
}
};
find_if
按条件查找
vector<Person>::iterator it =find_if(p.begin(), p.end(), fun_仿函数);
binary_search
二分查找 对于有序的数列
bool binary_search(it.beg(),it.end(),查找值)
bool ret = binary_search(v1.begin(), v1.end(), 8);
cout << ret << endl;
count
统计自定义类型时 需要配合重载operator==
自定义数据类型 就在自定义的类型里面就行仿函数的定义
内建型 就添加写一个类对运算符进行重载
count_if
class mix14 //谓词
{
public:
bool operator()(int v1) {
return v1 > 12;
}
};
int num =count_if(v1.begin(), v1.end(), mix14());
常用排序算法
sort(it.beg(),it.end(),谓词)
class ree {
public:
bool operator()(int v1, int v2) {
return v1 > v2;
}
};
sort(v1.begin(), v1.end(), ree());
- 洗牌算法
random_shuffle(it.beg(),it.end())
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);
- merge算法
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);
}
- reverse算法
reverse(target.begin(), target.end());
容器元素反转
常用的拷贝和替换算法
copy
容器内指定范围的元素拷贝到另一容器总·
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
//谓词
class mix5 {
public:
bool operator()(int val) {
return val > 5;
}
};
replace_if(tar3.begin(), tar3.end(), mix5(), 3000);
两个容器元素合并,并储存在另一个容器
内存模型和名称空间
单独编译
将组件函数放在独立的文件中,然后将他们链接成可执行的程序。如若要修改文件,只需重新头文件,然后将它与其他文件编译版本链接,程序的管理更为便捷
组织策略 可以把程序拆分为三个部分
- 头文件:编写函数原型 结构声明
eg:
#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
- 源代码文件:函数定义
eg:
#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;
}
- 源代码文件:main()和其他使用这些函数的函数放在第三个文件
#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)的体现
头文件包含的内容:
- 函数原型
- 结构声明
- 类声明
- 内联函数(~)
- 模板声明(~)
头文件编写
#ifndef HEAD_H_ //根据include名选择名称,并加上下划线 以防止被其他地方定义
#define HEAD_H_
…… //file content
#endif
名称空间 -ing
名称可以是函数、变量、结构、类以及类的成员,随着项目的增大,名称相互冲突的可能性也将增加
namespace jack{
double pail;
void fetch();
int pal;
struct well{}
}
名称空间是开放的(open),
- .
namespace jack{
char name[10];
}
- jack名称空间为fetch()函数提供原型。可以在文件后面再次使用Jack名称空间定义这个函数
namespace jack{
void fetch(){
……
}
}
using声明和using编译
- using声明由using和被限定的名称组成
using jack::pail; // 一个using声明
using声明将特定的名称添加到它所属的声明区域中。
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++
永远在使用对象之前先将它初始化
构造函数做好使用成员初始列,而不要在构造本体函数内使用赋值操作