C++编程笔记

C++编程小笔记

1、const简单使用
2、构造函数
3、this指针
4、friend友元
5、重载
6、继承
7、多态
8、文件读写
9、模板
10、异常
11、标准输入输出
12、回调函数
13、移动构造
14、静态成员
15、C++常用涉及模式
16、Cmake学习
17、C++20 - concept
18、using定义函数指针

1.const的应用
//栈区或静态存储区


void printStu(const Student *p) {   //使用const修饰,使得传入参数只读
    //p->name = "老王";  锁定p,使其只读
    cout << p->name << endl;
}
void printStu(const Student &p) {
    cout<<p.name<<endl;
}


void myConst(){
    int *const p1 = &a;//指针常量:指向不能改,值可以改
    *p1 = 100;

    const int *p2 = &b;//常量指针:值不能改,指向可改
    p2 = &a;
    
}
class Stu{
	mutable int name;		//mutable修饰的关键字,在常函数和常对象中都可以修改
	void getName()const{	//常函数,在类中不允许修改指针的指向和值
		this->name="张三";	//常函数中,仅允许修改用mutable修饰的关键字
	}
}
const Stu S;			//常对象,仅能调用常函数


//仿函数
class MyCompare {//仿函数,指能行使函数功能的类
public:
    //后面的const表示函数不会修改任何成员数据的值
    bool operator()(const int v1,const int v2) const {
        return v1 > v2;
    }
};
2.参数列表构造函数

函数调用顺序:父构造,子构造,子析构,父析构

class Person {
    int m_A, m_B, m_C;

    Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c) {//初始化列表构造函数

    }
};

C++在定义对象时,如果同时用=对其进行赋值,就相当于自动调用了相应的构造函数

#include <iostream>
using namespace std;
 class my{
public:
    my(){
        cout<<"无参构造"<<endl;
    }
   my(const char * s){
       cout<<"string构造函数"<<endl;
       this->ss=s;
   }
   my(int a){
       cout<<"int构造函数"<<endl;
       this->aa=a;
   }
   my(const my &m){
       cout<<"拷贝构造"<<endl;
       this->ss=m.ss;
       this->aa=m.aa;
   }
   my(const my &&m){
       cout<<"移动构造"<<endl;
       this->ss=move(m.ss);
       
       this->aa=m.aa;
   }
   my&operator=(int a){
       cout<<"operator="<<endl;
       this->aa=a;
   }
   string ss;
   int aa;
 };
int main()
{
    my a="123";
    my b=1;
    my c;
    c=1;
    return 0;
}

运行结果:

string构造函数
int构造函数
无参构造
operator=
Hello World
3.this指针

与Java的this指针功能相似

Persons &addAge(const Persons& p) {			//传入只读变量引用
        this->age += p.age;
        return *this;						//返回当前对象
    }
 this是指针常量,不可修改  //this=NULL 是不合法的
4.friend 友元

友元函数 类内申明类外实现

class Buliding{
public:
	House H;
	void visitBedRoom(){
		cout<<H.bedRoom<<endl;//因为Builing是House的友元类,所以可以访问私有成员
	}
}
class House {
    friend void getHouse(const House &H);
	friend class Building;//友元类,可以访问隐私成员
	
private:
    string bedRoom;
public:
    string bathRoom;

};

void getHouse(const House &H) {		//全局友元函数
    cout << H.bathRoom << endl << H.bedRoom << endl;
}
5.重载
//重载输出符


class myInt {
public:
    myInt &operator++();
    myInt(int _a) : A(_a) {
    }

    friend ostream &operator<<(ostream &os, myInt &my);

private:
    int A;
};

ostream &operator<<(ostream &os, myInt &my) {
    os << my.A;
    return os;
}

myInt &myInt::operator++() {
    this->A += 1;
    return *this;
}

//重载
class MyStr{
public:
	int id;
	char *name;
    MyStr& operator =(const MyStr& str)//赋值运算符
    {
        cout << "operator =" << endl;
        if (this != &str)				//二者不相等
        {
            if (name != NULL)
            {
                delete[] name;
            }
            this->id = str.id;
            int len = strlen(str.name);
            name = new char[len + 1];
            strcpy_s(name, strlen(str.name) + 1, str.name);
        }
        return *this;

 }
//通过函数重载运算符
Complex operator+(Complex &C) {                             
        Complex X(this->a + C.getA(), this->b + C.getB());
        return X;
    }


    T &operator[](int index) {
        return this->P[index];
    }
6.继承

在这里插入图片描述

父类中所有的非静态成员都会被继承成为子类的一部分,所以子类所占的大小位父类大小加上自身数据大小

  6.1处理同名元素
    cout << "Son下的 X = " << S.X << endl;
    cout << "Base下的 X = " << S.Base::X << endl;//通过这种方式访问父类中的同名元素
   //父类成员函数调用也是如此
    
//子类中访问父类同名成员
class Son : public Base {
public:
    Son() {
        X = 20;
        Base::X=30;
    }

    int X;
};
  • 静态成员也可以通过以上方式调用,还可以直接通过类名调用
  • 若出现同名成员函数,则子类函数会隐藏父类函数,调用父类函数则需要加作用域
    ( 子类名 ( 对象 ) :: 父类名 ( 对象 ) :: 函数名 )
  • 使用虚继承可以解决菱形继承的问题
6.1菱形继承

在这里插入图片描述

7.多态

多态为了写代码尽量遵循开闭原则,对修改关闭,对扩展开放,方便扩展和维护

class Animal {
public:
    virtual void Speak() {
        cout << "动物叫唤" << endl;
    }
};

class Cat : public Animal {
public :
    void Speak() {
        cout << "喵喵喵喵喵" << endl;
    }
};

class Dog : public Animal {
public:
    void Speak() {
        cout << "汪汪汪汪汪" << endl;
    }
};

void DoSpeak(Animal &A) {
    A.Speak();
}

//使用
Cat C;
DoSpeak(C);	//喵喵喵喵喵

Animal *A = new Dog();
A->Speak();

使用父类指针指向子类对象时,delete该对象不会执行子类的析构函数,有可能造成内存泄漏
为了解决这个问题,需要使用虚析构函数,即在父类析构函数前加上virtual关键字

8.文件读写
srand((unsigned) time(NULL));//随机数种子
rand() % 10000 + 10000

void test01() {
    ofstream ofs;
    string path = "/home/lhh/Project/C_C++/Test4_file/test.txt";
    ofs.open(path, ios::out);            //写文件
    ofs << "姓名:张三" << endl << "年龄:18" << endl;
    ofs.close();
}

void test02() {
    ifstream ifs;
    string path = "/home/lhh/Project/C_C++/Test4_file/test.txt";
    ifs.open(path, ios::in);
    if (!ifs.is_open()) {
        cout << "文件无法打开" << endl;
        return;
    } else {
        cout << "打开成功" << endl;
        char buff1[1024], buff2[1024];
        //方法一
//        while (ifs >> buff1) {        //逐行读
//            cout << buff1 << endl;    当一行大于1024时就不行了
//        }
        cout << "---------------" << endl;
        //方法二
//        while (ifs.getline(buff2, sizeof(buff2))) {
//            cout << buff2 << endl;
//        }
//      方法三
//        string str;
//        while(getline(ifs,str)){
//            cout<<str<<endl;
//        }
        char c;
        while ((c = ifs.get()) != EOF) {//逐字节读取
            cout << c;
        }
        ifs.close();
    }
}

二进制文件读写写入和读取对象

//二进制写入
void test03() {
    ofstream ofs;
    ofs.open("/home/lhh/Project/C_C++/Test4_file/Person.txt", ios::out | ios::binary);
    Person p[2] = {{"张三", 18, "男"},
                   {"李四", 20, "男"}};
    ofs.write((const char *) p, sizeof(Person));
    ofs.write((const char *) (p + 1), sizeof(Person));
    ofs.close();
}

//二进制读取
void test04() {
    ifstream ifs;
    ifs.open("/home/lhh/Project/C_C++/Test4_file/Person.txt", ios::in | ios::binary);
    if (!ifs.is_open()) {
        cout << "读取失败" << endl;
        return;
    }
    Person *p = new Person;
    for (int i = 0; i < 2 && !ifs.eof(); ++i) {
        ifs.read((char *) p, sizeof(Person));
        cout << p->name << endl << p->age << endl << p->sex << endl;
    }
	delete p;
    //cout << (p+1)->name << endl << (p+1)->age << endl << (p+1)->sex << endl;
    ifs.close();
}
  • String转为char数组的方法
    string myStr = "dasffdas";
    char S[myStr.length()+1];
    memset(S,'\0',myStr.length()+1);
    strncpy(S, &myStr[0], sizeof(myStr));//目标地址  源地址  复制字符数量
    cout << S << endl;

使用流持续写入文件时,由于数据并不是直接进入文件,而是先放进缓冲区,大量写入时可能会导致缓冲区数据被覆盖导致数据丢失,所以需要flush保证数据被写入文件

#include<fstream>
int main(){
	std::ofstream outfile("test.txt");
	for(int n=0; n<100; ++n{
		outfile<<n;
		outfile.flush();
	}
	outfile.close();
	return 0;
}
9.模板
//函数模板写法
template<typename T>
void Swap(T &S1,T &S2){
	T temp = S1;
	S1 = S2;
	S2 = temp;
}
int main(){
	int a = 10,b = 20;
	float c = 1.1,d = 2.2;
	//隐式转换
	Swap(a , b);
	//显示声明
	Swap<float>(c , d);
}


//类模板
template<class T1,class T2>
class Person{
	Person(T1 _name;T2 _age):name(_name),age(_age){//类内实现
	};
	T1 name;
	T2 age;
	void show();
}

//构造函数类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 _name,T2 _age):name(_name),age(_age){}

//成员函数类外实现
template<class T1,class T2>
void Person<T1,T2>::show(){
	cout<<"name: "<<name<<'\t'<<"age: "<<age<<endl;
}

10.异常机制
float Div(int a, int b) {
    if (0 == b) {
        throw b;    //抛出一个异常,可以是任何数据或者对象
    }
    return a / b;
}

void Div_use() {
    try {
        Div(10, 0);
    } catch (int e) {       	//接收异常,要用对应类型去接受,可以抛出多个不同类型的异常,
    							//也可以接收多个不同类型的异常
        cout << "除数e = " << e << endl;
    } catch (...){				//捕获所有异常
    	cout<<"未知类型异常!"<<endl;
    }
}

//--------------------------------------------------------------------------

void func() noexcept(false);//新写法,括号内写true就表示此函数不可
							//抛出异常,如果抛出,程序不会崩溃,而是自动终止

void func() {
	//函数实现
	...
}
11.标准输入输出

知识来源

//cin.get()                 //读入一个字符并返回它的值
//cin.get(一个参数)  //读入一个字符并把它存储在ch 
//cin.get(两个参数)  //可以读取字符串
//cin.get(三个参数)  //可以读字符串        
//cin.getline()
//cin.ignore()           //读取字符并忽略指定字符
//cin.peek()            //检查下一个输入的字符,不会把字符从流中移除
//cin.putback()       //返回一个字符给一个流
//cin.clear()  //清空cin缓冲


void test(){
	cout<<"输入字符或者数字"<<endl;
	char ch;
	cin.get(ch);//从缓冲区获取一个字符
	if(ch>='0' && ch<='9'){
		cin.putback(ch);
		int numget;
		cin>>number;
		cout<<"输入了数字"
	}
	else{
		char buf[256];
		cin>>buf;
		cout<<buf<<endl;
	}
}
12.回调函数
//定义函数指针
typedef int(*callback)(int);
//定义函数体
int pin(int x) {
    return x * x;
}
//实现回调
int getPinfang(int x, callback C) {
    return C(x);
}
//使用
void test() {
    getPinfang(10, pin);
}
13.移动构造

首先明确两个概念 左值右值

  • 左值:相当于一个盒子,代表内存空间
  • 右值:一个临时变量,一个值,带表的就是一个值
int a = 10;
a 就是左值,10 就是右值
class myClass{
int *memory = nullptr;			//当前类所占有的内存空间
myClass(myClass &&myclass){		//移动构造
	this->memory = myclass.memory;
	myclass.memory = nullptr;	//移动构造就是直接将你的东西取过来归我所用,然后在剥夺你的使用权
	}
};
14. 类的静态成员

静态成员变量必须在类中声明,在类外定义+初始化

class P{
public:
 static int i;
}
int P::i=100;

静态成员直接通过类名::变量名进行访问

cout<<"i = "<<P::i<<endl;
//赋值
P::i=20;

静态成员是所有类对象共享的

此外,还有静态成员函数

17.concept关键字

最简单的concept案例

template <typename T>
concept integral = std::is_integral_v<T>;
int a;
intergral<a>

判断传入的数据类型是否为int,是的话返回true
还有一种用法就是使用requires关键字

template<typename T>
concept printable=requires(T t){
    t.empty();
};
class A {
public:
    void empty() {
    }
};
class B {
public:
};

cout << "A has " << printable<A> << std::endl;
cout << "B has " << printable<B> << std::endl;

运行结果
在这里插入图片描述
判断传入的类型能否执行requires内的内容,能正常执行则返回true这里就是判断类内是否又成员函数empty(),还可以写为

template<typename T>
concept printable=requires(T t){
	//是否有empty函数
    t.empty();
    //是否有to_string函数且函数返回值为string
    {t.to_string()}->std::same_as<string>;
};
class A {
public:
    void empty() {
    }
};
class B {
public:
};

cout << "A has " << printable<A> << std::endl;
cout << "B has " << printable<B> << std::endl;

C++11中可以使用decltype+declval判断

template<class T>
struct has_val {
private:
    template<class U>
    static auto Check(int) -> decltype(std::declval<U>().val, std::true_type());//如果U有val,则编译通过返回true_type

    template<class U>
    static std::false_type Check(...);
public:
    enum {
        value = std::is_same<decltype(Check<T>(0)), std::true_type>::value
    };
};
class T;
int main(void){
	if(has_val<T>::value){
        cout<<"有val成员变量";
    }else{
    	cout<<"无val成员变量";
    }
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sinat_18811413/article/details/104606673
————————————————
版权声明:本文为CSDN博主「鸟哥01」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sinat_18811413/article/details/104606673
18.using定义函数指针
void S(int a,int b);
using i_i = void(*)(int,int);	//定义了一个函数指针,指向返回值为void,参数为int,int类型的函数
//
typedef (*i_i)(int,int);	//等价于上一个函数指针的定义

i_i f1 = S;

f1(1,2);					//调用

使用using关键字定义的函数指针,看着更加像一个赋值操作,便于理解


使用函数指针指向类的非静态重载成员函数


class GrilFriend : {
	std::string m_name;
public:
    explicit GrilFriend(std::string name);
    void hungry();
    void hungry(const string &str);
};
//这种方式可以直接区分两个重载的类成员函数
using hunger1 = void (GrilFriend::*)();
using hunger2 = void (GrilFriend::*)(const string &str);
hunger1 h1 = &GrilFriend::hungry;			//这样指向的就是hunger的无参版本
hunger2 h2 = &GrilFriend::hungry;
posted @ 2022-10-24 19:28  DaoDao777999  阅读(36)  评论(0编辑  收藏  举报