###override标签的使用
override是伪代码,所以是可写可不写的.它表示方法重写,写上会给我们带来好处.
1.可以当注释用,方便阅读.
2.告诉阅读你代码的人,这是方法的复写.
3.编译器可以给你验证override左面的方法名是否是你父类中所有的,如果没有则报错.
class MyWork :public QRunnable
{
Q_OBJECT
public:
explicit MyWork();
~MyWork();
void run() override;
}
###多重继承
class MyWork : public QObject, public QRunnable
{
Q_OBJECT
public:
explicit MyWork(QObject *parent = nullptr)
{
// 任务执行完毕,该对象自动销毁
setAutoDelete(true);
}
~MyWork();
void run() override{}
}
在上面的示例中 MyWork 类是一个多重继承,如果需要在这个任务中使用 Qt 的信号槽机制进行数据的传递就必须继承 QObject 这个类,如果不使用信号槽传递数据就可以不继承了,只继承 QRunnable 即可。
using的使用
-
变量别名
-
1. typedef unsigned int uint_t; 2. using uint_t=unsigned int;
-
-
函数指针
1.typedef int(*func_ptr)=(int,double); 2.using func_ptr=int(*)(int,double);
-
模板别名
//在C++98中,用tyhpedef定义别名只能用过一个外敷类实现 1. template<typename Val> 2. struct map_str_int // 外敷类 3. { 4. typedef std::map<std::string, Val> type; 5. }; 6. 7. map_str_int<int>::type map1;
//在C++11中,用using重定义模板 template<typename Val> using map_str_int = std::map<std::string, Val>; map_str_int<int> map1;
函数指针
-
定义:与变量类似,每一个函数都占用一段内存单元,拥有一个起始地址(入口地址),指向该起始地址的指针被称作函数指针。
-
语法:数据类型(*指针变量名)(参数表),数据类型指函数的返回值类型,如:
int (*p)(int a, int b); //p是一个指向函数的指针变量,所指函数的返回值类型为整型 int *p(int a, int b); //p是函数名,此函数的返回值类型为整型指针
-
在给函数指针变量赋值时,只需给出函数名,而不必给出参数。
int max(int x, int y); int (*p)(int a, int b); p = max;
-
在一个程序中,指针变量p可以先后指向不同的函数,但一个函数不能赋给一个不一致的函数指针(即不能让一个函数指针指向与其参数类型不一致的函数)。
如有如下的函数:int fn1(int x, int y); int fn2(int x); 定义如下的函数指针:int (*p1)(int a, int b); int (*p2)(int a); 则 p1 = fn1; //正确 p2 = fn2; //正确 p1 = fn2; //产生编译错误
-
-
-
应用
-
函数指针变量常用的用途之一是把指针作为参数传递到其他函数。
#include <iostream> using namespace std; #include <conio.h> int max(int x, int y); //求最大数 int min(int x, int y); //求最小数 int add(int x, int y); //求和 void process(int i, int j, int (*p)(int a, int b)); //应用函数指针 int main() { int x, y; cin>>x>>y; cout<<"Max is: "; process(x, y, max); cout<<"Min is: "; process(x, y, min); cout<<"Add is: "; process(x, y, add); getch(); return 0; } int max(int x, int y) { return x > y ? x : y; } int min(int x, int y) { return x > y ? y : x; } int add(int x, int y) { return x + y; } void process(int i, int j, int (*p)(int a, int b)) { cout<<p(i, j)<<endl; }
条件编译的使用
在头文件中引入条件编译,防止该头文件重复编译引发错误
-
using命名空间的引用
位于头文件的代码一般来说不应该使用using声明,这是因为头文件的内容会拷贝到所有引用他的文件中去,如果头文件里有某个using声明,那么每个使用了该头文件的文件就会都有这个声明,对于某些程序来说,由于不经意间包含了一些名字,反而会产生始料未及的冲突。
异常处理
- 标准异常类:标准库中提供了很多异常类,通过类继承组织起来
- try-catch
#include <iostream>
#include<vector>
#include<stdexcept>
using namespace std;
int main()
{
int item1,item2;
while(cin>>item1>>item2)
{
try{//将可能引发异常的方法或代码段放到try语句块中
if(item1==item2)
cout<<item1+item2<<endl;
else
{
throw runtime_error("Data must refer to same ISBN");//抛出异常
}
}catch(runtime_error err){
cout<<err.what()//err为runtime_error类对象
<<"\nTry Again?Enter y or n"<<endl;
char c;
cin>>c;
if(!cin||c=='n')
break;
}
}
return 0;
}
-
特性
跨函数???
逐级上抛,直到遇到异常处理语句,若不存在用户级别的异常处理,程序便会异常结束
异常捕捉严格按照类型匹配:throw一个对象或变量要用类或数据类型接。
class A{}; class B{}; int main() { try{ int j = 0; double d = 2.3; char str[20] = "Hello"; cout<<"Please input a exception number: "; int a; cin>>a; switch(a){ case 1: throw d; case 2: throw j; case 3: throw str; case 4: throw A(); case 5: throw B(); default: cout<<"No throws here.\n"; } } catch(int){ cout<<"int exception.\n"; } catch(double){ cout<<"double exception.\n"; } catch(char*){ cout<<"char* exception.\n"; } catch(A){ cout<<"class A exception.\n"; } catch(B){ cout<<"class B exception.\n"; } cout<<"That's ok.\n"; system("pause"); }
-
异常接口声明
“abcd”是char*类型的变量,或理解为数组指针
可同时throw多种类型的异常,catch()为捕获所有类型的异常
-
抛出捕获一个异常类
class MyException { public: MyException(char *str) { error=new char[strlen(str)+1];//在拷贝前先开辟/指定内存空间 strcpy(error,str);//将错误信息拷贝到内存空间中 } MyException(const MyException& ex)//复制构造函数 { this->error=new char[strlen(str)+1]; strcpy(this->error,ex.error); } MyException& operator=(const MyException& ex)//赋值操作符重载 { if(this->error!=NULL) { delete[] error; this->error=NULL; } this->error=new char[strlen(str)+1]; strcpy(this->error,ex.error); } void what() { cout<<error<<endl; } ~MyException() { if(error!=NULL) { delete[] error; this->error=NULL; } } protected: char *error; }; throw MyException("异常类型");//调用构造函数,生成匿名对象,抛出匿名对象 catch(MyException e)//若直接调用拷贝构造,e与匿名对象的error会指向同一块内存空间,导致两次析构,其实也可以把char换成string,用不到指针也就没这么多麻烦,或者用& e??? { e.what(); }
-
-
C++的标准异常类
建议自己的异常类要继承标准异常类
鲁棒性
计算机科学中,健壮性(英语:Robustness)是指一个计算机系统在执行过程中处理错误,以及算法在遭遇输入、运算等异常时继续正常运行的能力。 诸如模糊测试之类的形式化方法中,必须通过制造错误的或不可预期的输入来验证程序的健壮性。
友元
类交叉包含时用前向声明/前置声明
IO库
# include<iostream>//对可见域的操作
#include<fstream>//对文件进行的操作
#include<sstream>//对内存的操作
类型转换
c的强制类型转换:不管什么类型,都是Type b=(Type)a;
C++的类型转换:(cast———投掷)
-
static_cast 静态类型转换。如 int 转换成 char
用于内置数据类型与具有继承关系的指针或引用(子转父与父转子都行)
-
reinterpreter_cast 重新解释类型
-
dynamic_cast 命名上理解是动态类型转换。如子类和父类之间的多态类型转换,会进行安全检查。子类指针可以转换为父类指针(由大到小),转换后的类型安全
-
const_cast 字面上理解就是去 const 属性。
4 种类型转换的格式:
TYPE B = static_cast (a)
const类型
const char*, char const*, char*const的区别
-
指针常量:数据类型 * const 指针变量=变量名;
指向不能修改,指向的值可以修改
-
常量指针:const 数据类型 *指针变量=变量名;
指向常量的指针变量,指向可以修改,指向的值不能修改
-
常指针常量:const 数据类型 * const 指针变量=变量名;
指针不能改变,指针指向的值也不能改变
<span style="font-size:18px;">char *q; const char * const p="ABCDEF";//定义了一个常量常指针 q=p;//错误,试图讲一个常指针赋值给非常指针 p=q;//错误,试图修改指针常量的值,如1 *p=‘1’;//错误,试图修改指针指向的值,如2 p[1]='1';//错误,如2 p=NULL;//错误,如1</span>
C++中“:”的用法
-
继承方式,属性
-
构造函数赋初值
// 单链表 struct ListNode { int val; // 节点上存储的元素 ListNode *next; // 指向下一个节点的指针 ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数 };
:相当于val=x,next=NULL
###伪函数与lambda表达式
一、仿函数
仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。
仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载 operator() 运算符。因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符。
class StringAppend {
public:
explicit StringAppend(const string& str) : ss(str) {}
void operator() (const string& str) const {//重载()
cout << str << ' ' << ss << endl;
}
private:
const string ss;
};
int main() {
StringAppend myFunctor2("and world!");
myFunctor2("Hello");
}
二、lambda表达式
1 [capture list](params list)mutable exception->return type{function body}
2 [capture list](params list){function body}
3 [capture list]{function body}
格式1声明了const类型的表达式,这种类型的表达式不能修改捕获列表中的值。
- capture list:捕获外部变量列表:值捕获[变量],引用捕获[&变量],隐式捕获[&]、[=],混合方式
- params list:形参列表
- mutable指示符:用来说用是否可以修改捕获的变量
- exception:异常设定
- return type:返回类型
- function body:函数体
格式2省略了返回值类型,但编译器可以根据以下规则推断出Lambda表达式的返回类型:
(1):如果function body中存在return语句,则该Lambda表达式的返回类型由return语句的返回类型确定;
(2):如果function body中没有return语句,则返回值为void类型。
格式3中省略了参数列表,类似普通函数中的无参函数。
在Lambda表达式中传递参数还有一些限制,主要有以下几点:
- 参数列表中不能有默认参数
- 不支持可变参数
- 所有参数必须有参数名
{
int m = [](int x) { return [](int y) { return y * 2; }(x)+6; }(5);
std::cout << "m:" << m << std::endl; //输出m:16
std::cout << "n:" << [](int x, int y) { return x + y; }(5, 4) << std::endl; //输出n:9
auto gFunc = [](int x) -> function<int(int)> { return [=](int y) { return x + y; }; };
auto lFunc = gFunc(4);
std::cout << lFunc(5) << std::endl;
auto hFunc = [](const function<int(int)>& f, int z) { return f(z) + 1; };
auto a = hFunc(gFunc(7), 8);
int a = 111, b = 222;
auto func = [=, &b]()mutable { a = 22; b = 333; std::cout << "a:" << a << " b:" << b << std::endl; };
func();
std::cout << "a:" << a << " b:" << b << std::endl;
a = 333;
auto func2 = [=, &a] { a = 444; std::cout << "a:" << a << " b:" << b << std::endl; };
func2();
auto func3 = [](int x) ->function<int(int)> { return [=](int y) { return x + y; }; };
std::function<void(int x)> f_display_42 = [](int x) { print_num(x); };
f_display_42(44);
}
虚函数的作用
作用:允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数,即用基类指针指向子类对象,
[例12.2] 基类与派生类中有同名函数。在下面的程序中Student是基类,Graduate是派生类,它们都有display这个同名的函数。
#include <iostream>
#include <string>
using namespace std;
//声明基类Student
class Student
{
public:
Student(int, string,float); //声明构造函数
void display( );//声明输出函数
protected: //受保护成员,派生类可以访问
int num;
string name;
float score;
};
//Student类成员函数的实现
Student::Student(int n, string nam,float s)//定义构造函数
{
num=n;
name=nam;
score=s;
}
void Student::display( )//定义输出函数
{
/// cout<<"num:"<<num<<"\nname:"<<name<<"\nscore:"<<score<<"\n\n";
cout<<"num:"<<num<<"\nscore:"<<score<<"\n\n";
}
//声明公用派生类Graduate
class Graduate:public Student
{
public:
Graduate(int, string, float, float);//声明构造函数
void display( );//声明输出函数
private:float pay;
};
// Graduate类成员函数的实现
void Graduate::display( )//定义输出函数
{
cout<<"num:"<<num<<"\nname:"<<name<<"\nscore:"<<score<<"\npay="<<pay<<endl;
}
Graduate::Graduate(int n, string nam,float s,float p):Student(n,nam,s),pay(p){}
//主函数
int main()
{
Student stud1(1001,"Li",87.5);//定义Student类对象stud1
Graduate grad1(2001,"Wang",98.5,563.5);//定义Graduate类对象grad1
Student *pt=&stud1;//定义指向基类对象的指针变量pt
pt->display( );
pt=&grad1;
pt->display( );
return 0;
}
运行结果:
num:1001
score:87.5
num:2001
score:98.5
假如想输出grad1的全部数据成员,当然也可以采用这样的方法:通过对象名调用display函数,如grad1.display(),或者定义一个指向Graduate类对象的指针变量ptr,然后使ptr指向gradl,再用ptr->display()调用。这当然是可以的,但是如果该基类有多个派生类,每个派生类又产生新的派生类,形成了同一基类的类族。每个派生类都有同名函数display,在程序中要调用同一类族中不同类的同名函数,就要定义多个指向各派生类的指针变量。这两种办法都不方便,它要求在调用不同派生类的同名函数时采用不同的调用方式,正如同前面所说的那样,到不同的目的地要乘坐指定的不同的公交车,一一 对应,不能搞错。如果能够用同一种方式去调用同一类族中不同类的所有的同名函数,那就好了。
用虚函数就能顺利地解决这个问题。下面对程序作一点修改,在Student类中声明display函数时,在最左面加一个关键字virtual,即
virtual void display( );
这样就把Student类的display函数声明为虚函数。程序其他部分都不改动。再编译和运行程序,请注意分析运行结果:
num:1001
score:87.5
num:2001
name:Wang
score:98.5
pay=563.5
看!这就是虚函数的奇妙作用。现在用同一个指针变量(指向基类对象的指针变量),不但输出了学生stud1的全部数据,而且还输出了研究生grad1的全部数据,说明已调用了grad1的display函数。用同一种调用形式“pt->display()”,而且pt是同一个基类指针,可以调用同一类族中不同类的虚函数。这就是多态性,对同一消息,不同对象有 不同的响应方式。
说明:本来基类指针是用来指向基类对象的,如果用它指向派生类对象,则进行指针类型转换,将派生类对象的指针先转换为基类的指针,所以基类指针指向的是派生类对象中的基类部分。在程序修改前,是无法通过基类指针去调用派生类对象中的成员函数的。虚函数突破了这一限制,在派生类的基类部分中,派生类的虚函数取代了基类原来的虚函数,因此在使基类指针指向派生类对象后,调用虚函数时就调用了派生类的虚函数。 要注意的是,只有用virtual声明了虚函数后才具有以上作用。如果不声明为虚函数,企图通过基类指针调用派生类的非虚函数是不行的。
在面向对象的程序设计中,经常会用到类的继承,目的是保留基类的特性,以减少新类开发的时间。但是,从基类继承来的某些成员函数不完全适应派生类的需要,例如在例12.2中,基类的display函数只输出基类的数据,而派生类的display函数需要输出派生类的数据。过去我们曾经使派生类的输出函数与基类的输出函数不同名(如display和display1),但如果派生的层次多,就要起许多不同的函数名,很不方便。如果采用同名函数,又会发生同名覆盖。
利用虚函数就很好地解决了这个问题。可以看到:当把基类的某个成员函数声明为虚函数后,允许在其派生类中对该函数重新定义,赋予它新的功能,并且可以通过指向基类的指针指向同一类族中不同类的对象,从而调用其中的同名函数。由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数调用作出不同的响应。
虚函数的使用方法是:
- 在基类用virtual声明成员函数为虚函数。
这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。在类外定义虚函数时,不必再加virtual。 - 在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。
C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。 - 定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
- 通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。
通过虚函数与指向基类对象的指针变量的配合使用,就能方便地调用同一类族中不同类的同名函数,只要先用基类指针指向即可。如果指针不断地指向同一类族中不同类的对象,就能不断地调用这些对象中的同名函数。这就如同前面说的,不断地告诉出租车司机要去的目的地,然后司机把你送到你要去的地方。
范围for循环
-
格式:for(数据类型 变量:序列)
-
等价于:
for(int num:nums1)//等价于以下 for(int i=0;i<nums1.size();i++) { num=nums1[i]; }
-
序列:可以是花括号括起来的初始值列表、数组、
vector
、string
,这些类型的特点是拥有能返回迭代器的begin
和end
成员 -
数据类型、变量:序列中的每个元素都能转换成该变量的类型,最简单的方法是使用
auto
类型说明符。
若需要对序列中的元素进行写操作,则需要声明成引用类型&
,例如:vector<int>v={0,1,2,3,4}; *//因为要对v中的元素进行写操作,所以是引用类型* for(auto &r : v) r*= 2;
设计模式
1.单例模式
- 构造函数私有化
- 实例只有一份
1.懒汉模式
延迟加载,配置文件的实例只有用到时才会加载
#include <iostream>
using namespace std;
class father {
public:
static father* genFather();
father(){}
string getfathname()
{
if (fth != NULL)
{
return this->m_name;
}
return "未创建";
}
int getfatherage()
{
if (fth != NULL)
{
return this->m_age;
}
return NULL;
}
private:
father(int age,string name)//构造函数私有化
{
this->m_age = age;
this->m_name = name;
}
father(){}
protected:
string m_name;
int m_age;
static father* fth;
};
father* father::fth = nullptr;//static 成员变量必须在类声明的外部初始化
father* father::genFather()
{
if (fth == NULL)
fth = new father(10,"黎明");//只提供访问接口
return fth;
}
int main()
{
auto *_fth=father::genFather();
cout << _fth->genFather()->getfatherage() << endl;//调用的时候才会实例化
system("pause");
return 0;
}
2.饿汉式
一开始就创建了实例
class father {
public:
static father* genFather();
string getfathname()
{
if (fth != NULL)
{
return this->m_name;
}
return "未创建";
}
int getfatherage()
{
if (fth != NULL)
{
return this->m_age;
}
return NULL;
}
private:
father(int age, string name)//构造函数私有化
{
this->m_age = age;
this->m_name = name;
}
father(){}
protected:
string m_name;
int m_age;
static father* fth;
};
father* father::fth = new father(10, "黎明");// 提前创建实例
father* father::genFather()
{
if (fth == NULL)
fth = new father(10, "黎明");//只提供访问接口
return fth;
}
int main()
{
auto* _fth = father::genFather();
cout << _fth->genFather()->getfatherage() << endl;
system("pause");
return 0;
}
####2.简单工厂模式
- 一个类别对应一个工厂
- 不亲自创建对象,用的时候让工厂去创建
- 原来需要自己创建对象,现在不需要自己创建对象,二十创建这个对象的工厂
###STL
四大功能:增删查改
哈希表
功能:快速判断一个元素是否出现在集合中
key_value结构unoudered_map
-
特点:无序不可重复,查询效率与增删效率为O(1)
-
简单使用
std::unordered_map<std::string, std::int> umap; //定义 umap.insert(Map::value_type("test", 1));//增加 //根据key删除,如果没找到n=0 auto n = umap.erase("test") //删除 auto it = umap.find(key) //改 if(it != umap.end()) it->second = new_value; //map中查找x是否存在 umap.find(x) != map.end()//查 //或者 umap.count(x) != 0
if (fth == NULL)
fth = new father(10, “黎明”);//只提供访问接口
return fth;
}
int main()
{
auto* _fth = father::genFather();
cout << _fth->genFather()->getfatherage() << endl;
system(“pause”);
return 0;
}
####2.简单工厂模式
- 一个类别对应一个工厂
- 不亲自创建对象,用的时候让工厂去创建
- 原来需要自己创建对象,现在不需要自己创建对象,二十创建这个对象的工厂
###STL
四大功能:增删查改
#### 哈希表
功能:快速判断一个元素是否出现在集合中
##### key_value结构unoudered_map
- 特点:无序不可重复,查询效率与增删效率为O(1)
- 简单使用
std::unordered_map<std::string, std::int> umap; //定义
umap.insert(Map::value_type(“test”, 1));//增加
//根据key删除,如果没找到n=0
auto n = umap.erase(“test”) //删除
auto it = umap.find(key) //改
if(it != umap.end())
it->second = new_value;
//map中查找x是否存在
umap.find(x) != map.end()//查
//或者
umap.count(x) != 0