C++学习笔记

###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.   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;
    

    函数指针

    1. 定义:与变量类似,每一个函数都占用一段内存单元,拥有一个起始地址(入口地址),指向该起始地址的指针被称作函数指针。

    2. 语法:数据类型(*指针变量名)(参数表),数据类型指函数的返回值类型,如:

      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; //产生编译错误
        
  1. 应用

    • 函数指针变量常用的用途之一是把指针作为参数传递到其他函数。

      #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;
      }
      

      条件编译的使用

      在头文件中引入条件编译,防止该头文件重复编译引发错误

      image-20220703211522300

using命名空间的引用

位于头文件的代码一般来说不应该使用using声明,这是因为头文件的内容会拷贝到所有引用他的文件中去,如果头文件里有某个using声明,那么每个使用了该头文件的文件就会都有这个声明,对于某些程序来说,由于不经意间包含了一些名字,反而会产生始料未及的冲突。

异常处理

标准异常类库:

  • 标准异常类:标准库中提供了很多异常类,通过类继承组织起来
  1. 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;
}

image-20220714102806089

  1. 特性

    跨函数???

    逐级上抛,直到遇到异常处理语句,若不存在用户级别的异常处理,程序便会异常结束

    异常捕捉严格按照类型匹配: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");
    }
    
  2. 异常接口声明

    “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();
      }
      
  3. C++的标准异常类

    建议自己的异常类要继承标准异常类

鲁棒性

计算机科学中,健壮性(英语:Robustness)是指一个计算机系统在执行过程中处理错误,以及算法在遭遇输入、运算等异常时继续正常运行的能力。 诸如模糊测试之类的形式化方法中,必须通过制造错误的或不可预期的输入来验证程序的健壮性。

友元

类交叉包含时用前向声明/前置声明

image-20220715092257293

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

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++中“:”的用法

  1. 继承方式,属性

  2. 构造函数赋初值

    // 单链表
    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类型的表达式,这种类型的表达式不能修改捕获列表中的值。

  1. capture list:捕获外部变量列表:值捕获[变量],引用捕获[&变量],隐式捕获[&]、[=],混合方式
  2. params list:形参列表
  3. mutable指示符:用来说用是否可以修改捕获的变量
  4. exception:异常设定
  5. return type:返回类型
  6. function body:函数体

格式2省略了返回值类型,但编译器可以根据以下规则推断出Lambda表达式的返回类型:

(1):如果function body中存在return语句,则该Lambda表达式的返回类型由return语句的返回类型确定;

(2):如果function body中没有return语句,则返回值为void类型。

格式3中省略了参数列表,类似普通函数中的无参函数。

在Lambda表达式中传递参数还有一些限制,主要有以下几点:

  1. 参数列表中不能有默认参数
  2. 不支持可变参数
  3. 所有参数必须有参数名
{
     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),但如果派生的层次多,就要起许多不同的函数名,很不方便。如果采用同名函数,又会发生同名覆盖。

利用虚函数就很好地解决了这个问题。可以看到:当把基类的某个成员函数声明为虚函数后,允许在其派生类中对该函数重新定义,赋予它新的功能,并且可以通过指向基类的指针指向同一类族中不同类的对象,从而调用其中的同名函数。由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数调用作出不同的响应。

虚函数的使用方法是:

  1. 在基类用virtual声明成员函数为虚函数。
    这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。在类外定义虚函数时,不必再加virtual。
  2. 在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。
    C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。
  3. 定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
  4. 通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。
    通过虚函数与指向基类对象的指针变量的配合使用,就能方便地调用同一类族中不同类的同名函数,只要先用基类指针指向即可。如果指针不断地指向同一类族中不同类的对象,就能不断地调用这些对象中的同名函数。这就如同前面说的,不断地告诉出租车司机要去的目的地,然后司机把你送到你要去的地方。
范围for循环
  • 格式:for(数据类型 变量:序列)

  • 等价于:

    for(int num:nums1)//等价于以下
    for(int i=0;i<nums1.size();i++)
    {
    	num=nums1[i];
    }
    
  • 序列:可以是花括号括起来的初始值列表、数组、vectorstring,这些类型的特点是拥有能返回迭代器beginend成员

  • 数据类型、变量:序列中的每个元素都能转换成该变量的类型,最简单的方法是使用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




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值