当前位置: 首页 > article >正文

C++朝花夕拾

目录

目录

函数分文件编写

野指针

const与指针 

const修饰指针——常量指针

const修饰常量——指针常量

const既修饰指针,又修饰常量

const阻止函数修改

delete和delete[]的区别

内存四区(面试会问?)

程序运行前

代码区

全局区

程序运行后 

栈区

堆区

new、delete——开辟、释放堆区内存

引用

常量引用

封装

访问权限

class和struct的区别

拷贝构造函数

调用时机

构造函数调用规则

深拷贝和浅拷贝(面试经典问题)

错误场景

解决

简化构造函数——初始化列表

语法

类中嵌套类对象

静态成员——前加static关键字

成员变量和成员函数分开存储

const修饰成员函数

友元

全局函数做友元

类做友元

成员函数做友元

运算符重载(非常重要)

加号运算符重载

左移运算符重载

递增运算符重载(int占位参数的作用)

赋值运算符重载(注意深浅拷贝的问题)

函数调用运算符重载和匿名函数对象

匿名函数对象

继承

继承方式

从父类继承过来的成员,哪些属于子类对象中?

父类和子类的构造和析构顺序是谁先谁后?

菱形继承(面试会考?)

虚继承——解决菱形问题

底层原理

多态(面试会问?)

原理剖析(重要)  

空类和不带虚函数的空类sizeof值为1,为什么?

带虚函数的空类sizeof值为多少?为什么?​编辑

虚函数实现多态原理剖析(重要)

纯虚函数和抽象类

概念

虚析构和纯虚析构

使用场景

解决

C++文件操作

职工管理系统

gitee项目地址

遇到的问题

1.抽象类(含有纯虚函数)无法实例化,如何创建数组来存放该抽象类的子类对象?

2.注意命名空间问题,有的结构漏掉命名空间可能会报语法错误(太恶心了!!!)

3.使用stringsteam拼接字符串

4.从文件中读取数据,这块真的恶心我半天,wcnm!!!

lambda表达式



函数分文件编写

作用:让代码结构更加清晰

 example:

野指针

 

const与指针 

 const修饰指针有三种情况:

1.const修饰指针——常量指针

2.const修饰常量——指针常量

3.const既修饰指针,又修饰常量

const修饰指针——常量指针

example:

int a=10;
int b=10;
const int *p = &a;

在指针前面加上const限定

特点:

指针的指向可以修改,但是指针指向的值不可以修改。

可以修改指针的指向

 不可以修改指针指向的值

const修饰常量——指针常量

example:

int a = 10;
int b = 10;
int * const p = &a;

在变量之前加上const限定

特点:

指针的指向不可以修改,指向的值可以修改

const既修饰指针,又修饰常量

example:

int a = 10;
int b = 10;
const int* const p = &a;

在指针和变量的前面都加const限定

特点:

指针的指向不可以修改,指针的值也不能修改

const修饰*,*p不能修改——常量指针

const修饰p,p不能修改(指向的位置不可以修改)——指针常量

const阻止函数修改

传进来的参数前加const可以防止参数被修改(在传送为指针或者引用的情况下)

delete和delete[]的区别

只要记住new和delete一起用,new[]和delete[]一起用就行了

内存四区(面试会问?)

  

程序运行前

程序编译后,生成exe可执行程序,未执行exe前分为两个区域:代码区、全局区

代码区

特点:存放CPU机器指令、共享、只读

共享:exe可能会频繁执行,在内存中有一份代码即可

只读:防止指令被意外修改

全局区

存放全局变量、静态变量(static修饰)、常量(字符串常量)

特点:全局区的数据在程序结束后由操作系统释放

const修饰的局部变量不在全局区中

程序运行后 

栈区

注意

不要返回局部变量的地址,栈区开辟的数据由编译器自动释放。

堆区

new、delete——开辟、释放堆区内存

引用

本质:引用的本质在C++内部实现是一个指针常量(不可以修改指针的指向,但可以修改指向的值)

常量引用

使用场景:用来修饰形参,防止误操作 

ex:

const防止val被修改 

封装

访问权限

有三种:

public(公共):成员类内可以访问,类外也可以访问

protected(保护):类内可以访问,类外不可以访问  儿子也可以访问父亲中的保护内容

private(私有):类内可以访问,类外不可以访问 儿子不能访问父亲中的私有内容

class和struct的区别

拷贝构造函数

调用时机

C++中拷贝构造函数调用时机通常有三种情况

  • 使用一个已经创建完毕的对象来初始化一个新对象(主动调用)

  • 值传递的方式给函数参数传值

  • 以值方式返回局部对象

第三点注意!有的编译器会进行优化,此时可能不会调用拷贝构造函数(我用的VS2022是会这样的)

ex:

class Person {
public:
	Person() {
		cout << "无参构造函数!" << endl;
		mAge = 0;
	}
	Person(int age) {
		cout << "有参构造函数!" << endl;
		mAge = age;
	}
	Person(const Person& p) {
		cout << "拷贝构造函数!" << endl;
		mAge = p.mAge;
	}
	//析构函数在释放内存之前调用
	~Person() {
		cout << "析构函数!" << endl;
	}
public:
	int mAge;
};

//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {

	Person man(100); //p对象已经创建完毕
	Person newman(man); //调用拷贝构造函数
	Person newman2 = man; //拷贝构造

	//Person newman3;
	//newman3 = man; //不是调用拷贝构造函数,赋值操作
}

//2. 值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1) {}
void test02() {
	Person p; //无参构造函数
	doWork(p);
}

//3. 以值方式返回局部对象
Person doWork2()
{
	Person p1;
	cout << (int *)&p1 << endl;
	return p1;
}

void test03()
{
	Person p = doWork2();
	cout << (int *)&p << endl;
}


int main() {

	//test01();
	//test02();
	test03();

	system("pause");

	return 0;
}

构造函数调用规则

默认情况下,c++编译器至少给一个类添加3个函数

1.默认构造函数(无参,函数体为空)

2.默认析构函数(无参,函数体为空)

3.默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

  • 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造

  • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数

深拷贝和浅拷贝(面试经典问题)

浅拷贝:简单的赋值拷贝操作

深拷贝:在堆区重新申请空间,进行拷贝操作   

错误场景

#include<iostream>
using namespace std;

class Person
{
public:
	Person(int age, int height)
	{
		this->age = age;
		this->height = new int(height);
	}

	~Person()
	{
		if (this->height != NULL)
		{
			delete this->height;
			this->height = NULL;
		}
	}

private:
	int age;
	int* height;
};

int main()
{
	Person p1(18,160);
	Person p2 = p1;

	return 0;
}

运行程序产生报错,原因:

程序在结束时会自动执行析构函数,先释放p2再释放p1(栈中先进后出) 

会对0x0011这块地址调用两次delete导致程序出错



解决

自己写一个拷贝构造函数,进行深拷贝

简化构造函数——初始化列表

语法

构造函数():属性1(值1),属性2(值2)... {}

example:

class Person {
public:

	传统方式初始化
	//Person(int a, int b, int c) {
	//	m_A = a;
	//	m_B = b;
	//	m_C = c;
	//}

	//初始化列表方式初始化
	Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
	void PrintPerson() {
		cout << "mA:" << m_A << endl;
		cout << "mB:" << m_B << endl;
		cout << "mC:" << m_C << endl;
	}
private:
	int m_A;
	int m_B;
	int m_C;
};

int main() {

	Person p(1, 2, 3);
	p.PrintPerson();


	system("pause");

	return 0;
}

类中嵌套类对象

class A {}
class B
{
    A a;
}

创建并由系统销毁一个B对象,则:

1.调用A对象的构造函数

2.调用B对象的构造函数

3.调用B对象的析构函数

4.调用A对象的析构函数

静态成员——前加static关键字

  • 静态成员变量

    • 所有对象共享同一份数据

    • 在编译阶段分配内存

    • 类内声明,类外初始化

  • 静态成员函数

    • 所有对象共享同一个函数

    • 静态成员函数只能访问静态成员变量

成员变量和成员函数分开存储

class Person {
public:
	Person() {
		mA = 0;
	}
	//非静态成员变量占对象空间
	int mA;
	//静态成员变量不占对象空间
	static int mB; 
	//函数也不占对象空间,所有函数共享一个函数实例
	void func() {
		cout << "mA:" << this->mA << endl;
	}
	//静态成员函数也不占对象空间
	static void sfunc() {
	}
};

int main() {

	cout << sizeof(Person) << endl;

	system("pause");

	return 0;
}

const修饰成员函数

常函数:

  • 成员函数后加const后我们称为这个函数为常函数

  • 常函数内不可以修改成员属性

  • 成员属性声明时加关键字mutable后,在常函数中

    依然可以修改

常对象:

  • 声明对象前加const称该对象为常对象

  • 常对象只能调用常函数

代码示例

#include<iostream>
using namespace std;

class Person
{
public:
	void myFunc1() const
	{
		//a = 10; 报错,常函数不能修改不带mutable的成员变量
		b = 10;
	}

	void myFunc2()
	{

	}
private:
	int a;
	mutable int b;

};

int main()
{
	const Person p;//声明常对象
	//p.myFunc2();报错,常对象只能调用常函数
	p.myFunc1()

}

友元

目的:

让一个函数或者类 访问另一个类中私有成员

关键字:friend

实现方式:

  • 全局函数做友元

  • 类做友元

  • 成员函数做友元

全局函数做友元

在类中用friend关键字申明友元函数,在类外初始化。

代码示例:

#include<iostream>
using namespace std;

class Building
{
	friend void goodGay(Building &b);
public:
	int livingRoom;
private:
	int bedRoom;
};

void goodGay(Building& b)
{
	b.livingRoom = 1;
	cout << "我到客厅来了!" << endl;
	b.bedRoom = 1;
	cout << "我到卧室来了!" << endl;
}
int main()
{
	Building b; 
	goodGay(b);
	return 0;
}

类做友元

在类中申明友元

friend class GoodGay;

代码示例

#include<iostream>
using namespace std;
class GoodGay;
class Building
{
	friend class GoodGay;
public:
	int livingRoom;
private:
	int bedRoom;

};
class GoodGay
{
private:
	Building b;
public:
	void visit()
	{
		b.livingRoom = 1;
		cout << "我到客厅来了!" << endl;
		b.bedRoom = 1;
		cout << "我到卧室来了!" << endl;

	}
};
int main()
{
	GoodGay gg;
	gg.visit();


	return 0;
}

成员函数做友元

代码示例

#include<iostream>
#include<string>
using namespace std;

class GoodGay
{
public:
	GoodGay();
	void visit1();//申请为友元成员函数
	void visit2();//不做处理
private:
	Building* b;
};

class Building
{
	friend void GoodGay::visit1();
public:
	Building();
	string livingRoom;
private:
	
	string bedRoom;
};

GoodGay::GoodGay()
{
	b = new Building;
}
Building::Building()
{
	livingRoom = "客厅";
	bedRoom = "卧室";
}
void GoodGay::visit1()//友元函数可以访问私有属性
{
	cout << "访问" << b->livingRoom << endl;
	cout << "访问" << b->bedRoom << endl;
}
void GoodGay::visit2()
{
	cout << b->livingRoom << endl;
	//cout << b.bedRoom << endl; 非友元函数无法访问私有属性

}
int main()
{
	GoodGay gg;
	gg.visit1();
	return 0;
}

运算符重载(非常重要)

什么样的运算符可以用成员函数进行重载??

对象要在运算符的前面,如+、-、*

以加号为例,用成员函数重载+,则p3=p1+p2相当于p3=p1.operator+(p2)

成员函数重载运算符,则对象为运算符前的参数

又如:左移运算符无法使用成员函数运算符重载

cout<<p,运算符前的参数为cout而不为对象。

加号运算符重载

#include<iostream>
using namespace std;

class Person
{
public:
	int m_A;
	int m_B;
	Person()
	{}
	
	Person(int a, int b)
	{
		this->m_A = a;
		this->m_B = b;
	}
	
	//成员函数实现运算符重载
	//Person operator+(Person& p)
	//{
	//	Person tmp;
	//	tmp.m_A = this->m_A + p.m_A;
	//	tmp.m_B = this->m_B + p.m_B;
	//	return tmp;
	//}
	//


};
//全局运算符重载
Person& operator+(Person& p1,Person& p2)
{
	Person tmp;
	tmp.m_A = p1.m_A + p2.m_A;
	tmp.m_B = p1.m_A + p2.m_B;
	return tmp;
}

int main()
{
	Person p1(10,10);
	Person p2(5,15);
	Person p3 = p1 + p2;
	cout << p3.m_A << endl;
	cout << p3.m_B << endl;
	Person p4 = p1 + p2 + p3;
	return 0;
}

左移运算符重载

通常不会用成员函数重载左移运算符

#include<iostream>
#include<string>
using namespace std;

class Person
{
private:
	string name;
	int age;
public:

	friend ostream& operator<<(ostream& cout, Person& p);
	Person(string name,int a)
	{
		this->name = name;
		this->age = a;
	}

	int getAge()
	{
		return this->age;
	}

	string getName()
	{
		return this->name;
	}

};

ostream& operator<<(ostream& cout, Person& p)
{
	cout << "姓名:" << p.name << " 年龄:" << p.age;
	return cout;
}


int main()
{ 
	Person p("叶盛",25);
	cout << p << endl;
	return 0;
}

递增运算符重载(int占位参数的作用)

1.用int占位符来区分前置和后置递增

2.前置递增返回引用,后置递增返回值

class MyInteger {

	friend ostream& operator<<(ostream& out, MyInteger myint);

public:
	MyInteger() {
		m_Num = 0;
	}
	//前置++
	MyInteger& operator++() {
		//先++
		m_Num++;
		//再返回
		return *this;
	}

	//后置++
	MyInteger operator++(int) {
		//先返回
		MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
		m_Num++;
		return temp;
	}

private:
	int m_Num;
};


ostream& operator<<(ostream& out, MyInteger myint) {
	out << myint.m_Num;
	return out;
}


//前置++ 先++ 再返回
void test01() {
	MyInteger myInt;
	cout << ++myInt << endl;
	cout << myInt << endl;
}

//后置++ 先返回 再++
void test02() {

	MyInteger myInt;
	cout << myInt++ << endl;
	cout << myInt << endl;
}

int main() {

	test01();
	//test02();

	system("pause");

	return 0;
}

赋值运算符重载(注意深浅拷贝的问题)

注意

1.赋值前的对象堆区有数据应先释放

2.深浅拷贝的问题

#include<iostream>
#include<string>
using namespace std;

class Person
{
private:
	string name;
	int* age;
public:
	Person()
	{

	}
	Person(string name, int a)
	{
		this->name = name;
		this->age = new int(a);
	}
	~Person()
	{
		if (age != NULL)
		{
			delete age;
			age = NULL;
		}
	}

	Person& operator=(Person& p)
	{
		if (age != NULL)
		{
			delete age;
			age = NULL;
		}
		this->age = new int(*p.age);
		this->name = p.name;
		return *this;
	}

	
};

int main()
{
	Person p1("叶盛",18);
	Person p2;
	Person p3;
	p3 = p2 = p1;
	return 0;
}

函数调用运算符重载和匿名函数对象

  • 函数调用运算符 () 也可以重载

  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数

  • 仿函数没有固定写法,非常灵活

    class MyPrint
    {
    public:
    	void operator()(string text)
    	{
    		cout << text << endl;
    	}
    
    };
    void test01()
    {
    	//重载的()操作符 也称为仿函数
    	MyPrint myFunc;
    	myFunc("hello world");
    }
    
    
    class MyAdd
    {
    public:
    	int operator()(int v1, int v2)
    	{
    		return v1 + v2;
    	}
    };
    
    void test02()
    {
    	MyAdd add;
    	int ret = add(10, 10);
    	cout << "ret = " << ret << endl;
    
    	//匿名对象调用  
    	cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
    }
    
    int main() {
    
    	test01();
    	test02();
    
    	system("pause");
    
    	return 0; 
    }

    匿名函数对象

不创建仿函数对象,直接使用

仿函数类名()(参数1,参数2,参数3...)来执行函数 

继承

继承方式

 无论哪种继承方式都不能继承私有成员

公共继承:公共成员和保护成员不变,私有成员访问不到

保护继承:公共成员和保护成员均变为保护成员,私有成员访问不到

私有继承:公共成员和保护成员均变为私有成员,私有成员访问不到

从父类继承过来的成员,哪些属于子类对象中?

父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到

父类和子类的构造和析构顺序是谁先谁后?

继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反

1.父类构造

2.子类构造

3.子类析构

4.父类析构

菱形继承(面试会考?)

两个派生类继承同一个基类

又有某个类同时继承者两个派生类

这种继承被称为菱形继承,或者钻石继承

  

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。\

可以加作用域来解决:

st.Sheep::age=10;

st.Tuo::age=20;

     2.草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

可以用虚继承解决菱形继承问题

虚继承——解决菱形问题

继承之前加上关键字virutal变为虚继承

exm:

class Sheep:virtual public Animal

{

    .....
}

这里Animal类称为虚基类

示例

class Animal
{
public:
	int m_Age;
};

//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal {};
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 = " << st.Sheep::m_Age << endl;
	cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;
	cout << "st.m_Age = " << st.m_Age << endl;
}


int main() {

	test01();

	system("pause");

	return 0;
}

底层原理

SheepTuo(羊驼)从Sheep(羊)类继承了一个vbptr(虚基类指针),从 Tuo(驼)类也继承了一个vbptr(虚基类指针),这两个指针分别指向一张虚基类表,表中有一个偏移量

两个地址(0、8)分别加上偏移量,指向同一个m_Age

多态(面试会问?)

 多态分为两类

  • 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名

  • 动态多态: 派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址

  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

动态多态条件:

1.有继承关系  eg:猫类继承动物类

2.子类重写父类的虚函数

3.父类的指针或者引用指向子类对象

重写:函数返回值类型、函数名、参数列表 完全相同

代码示例

#include<iostream>
using namespace std;

class Animal
{
public:
	void doSpeak()
	{
		cout << "动物在说话." << endl;
	}
};

class Cat :public Animal
{
public:
	void doSpeak()
	{
		cout << "小猫在说话." << endl;
	}
};

void test01(Animal a)
{
	a.doSpeak();
}


int main()
{
	Cat c;
	test01(c);
	return 0;
}

  输出结果

 分析

执行说话的函数

void test01(Animal a)
{
	a.doSpeak();
}

地址早绑定——在编译阶段就确定函数的地址

如果想执行让猫说话,则需要用虚函数(virtual)实现函数地址晚绑定(在运行阶段绑定)

原理剖析(重要)  

空类和不带虚函数的空类sizeof值为1,为什么?

#include<iostream>
using namespace std;
class Animal
{
public:
	void speak()
	{
		cout << "动物在说话!" << endl;
	}

};

int main()
{
	cout << "sizeof(Animal) = " << sizeof(Animal) << endl;
	return 0;
}

 

这里成员函数不属于该类,成员函数是共享的。

带虚函数的空类sizeof值为多少?为什么?

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

带一个虚函数的空类大小为8,这里是一个vfptr(虚函数)指针的大小,指向一个虚函数表。

虚函数实现多态原理剖析(重要)

#include<iostream>
using namespace std;
class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在说话!" << endl;
	}


};

class Cat :public Animal
{
	
};

void test01(Animal& a)
{
	a.speak();
}

int main()
{
	Cat cat;
	test01(cat);
	return 0;
}

动物类在内存中的结构为

PS:size这里为指针的大小,视频里为32位机,4个字节。

可以看到动物类有一个vfptr(虚函数指针)指向一张(vftable)虚函数表

表中有Animal类的speak函数地址

猫类继承了动物类,继承了一个虚函数指针指向自己的虚函数表,在没有重写speak函数之前,结构如下:

此时猫的虚函数表中存的是动物类的speak函数的地址

对speak函数进行重写后,猫的结构如下 

此时猫类的虚函数表中记录的是猫类的speak函数的地址。

 执行test01函数就会输出猫在叫。

纯虚函数和抽象类

概念

通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数.

当类中有了纯虚函数,这个类也称为抽象类.

纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;

抽象类特点

  • 无法实例化对象

  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

代码示例

#include<iostream>
using namespace std;
class Animal
{
public:
	virtual void speak() = 0;


};

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

void test01(Animal& a)
{
	a.speak();
}

int main()
{
	Cat cat;
	test01(cat);
	return 0;
}

这里speak为纯虚函数,Animal为抽象类。

虚析构和纯虚析构

如果子类中有属性开辟到堆区,那么父类指针在释放时(delete父类指针)无法调用到子类的析构代码

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象

  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名() = 0;

类名::~类名(){}

使用场景

#include<iostream>
#include<string>
using namespace std;

class Animal
{
public:
	Animal()
	{
		cout << "动物类的构造函数调用." << endl;
	}

	virtual void speak()
	{
		cout << "动物在说话!" << endl;
	}

	~Animal()
	{
		cout << "动物类的析构函数调用." << endl;
	}
};

class Cat :public Animal
{
public:
	Cat(string name)
	{
		m_Name = new string(name);
		cout << "猫类的构造函数被调用." << endl;
	}
	string* m_Name;
	~Cat()
	{
		if (m_Name != NULL)
		{
			delete m_Name;
			m_Name = NULL;
		}
			
		cout << "猫类的析构函数调用." << endl;
	}

	void speak()
	{
		cout <<*m_Name<< "猫在说话!" << endl;
	}
};

void test01()
{
	Animal* cat = new Cat("Tom");
	delete cat;
}

int main()
{
	test01();
	return 0;
}

输出结果

用父类(Animal)指针指向子类(Cat)对象cat,然后delete对象cat,结果只调用了父类(Animal)的析构函数,造成了内存泄漏。

解决

将父类中的析构函数改为虚析构或者纯虚析构

注意!无论是虚析构还是纯虚析构都要代码实现!

#include<iostream>
#include<string>
using namespace std;

class Animal
{
public:
	Animal()
	{
		cout << "动物类的构造函数调用." << endl;
	}

	virtual void speak()
	{
		cout << "动物在说话!" << endl;
	}

	/*
	虚析构
	virtual ~Animal()
	{
		cout << "动物类的析构函数调用." << endl;
	}*/
	
	//纯虚析构
	virtual ~Animal() = 0;
};

//!纯虚析构函数也需要具体实现!
Animal::~Animal()
{
	cout << "动物类的析构函数调用." << endl;
}

class Cat :public Animal
{
public:
	Cat(string name)
	{
		m_Name = new string(name);
		cout << "猫类的构造函数被调用." << endl;
	}
	string* m_Name;
	~Cat()
	{
		if (m_Name != NULL)
		{
			delete m_Name;
			m_Name = NULL;
		}
			
		cout << "猫类的析构函数调用." << endl;
	}

	void speak()
	{
		cout <<*m_Name<< "猫在说话!" << endl;
	}
};

void test01()
{
	Animal* cat = new Cat("Tom");
	delete cat;
}

int main()
{
	test01();
	return 0;
}

  

C++文件操作

笔记链接

C++文件操作-CSDN博客

职工管理系统

gitee项目地址

https://gitee.com/caodanxiao123/Employee-Management-System.git

遇到的问题

1.抽象类(含有纯虚函数)无法实例化,如何创建数组来存放该抽象类的子类对象?

可以创建抽象类指针的数组

 

2.注意命名空间问题,有的结构漏掉命名空间可能会报语法错误(太恶心了!!!)

注意:我的编程习惯是头文件的编写中不加using namespace std;有很多C++的结构是std命名空间的,如果漏掉std可能会报语法错误或者头文件和源文件不兼容的错误。

我在头文件中定义类ManagementSystem,有一个成员变量类型为fstream*

这个报错也是非常的抽象,最后问题是fstream没有加上命名空间std,编译器无法识别。

3.使用stringsteam拼接字符串

4.从文件中读取数据,这块真的恶心我半天,wcnm!!!

对于:

1.从文件中按行读取数据,getline有很多版本,fstream的getline,std命名空间下的getline研究用法太麻烦,以后暂时统一使用std命名空间下的getline(文件流 ,std::string )

2.对于字符串格式化也有很多种方法,以后统一使用C语言的sscanf_s

int sscanf_s(  
    const char *buffer,  
    const char *format,  
    ...  
);

buffer为要提取数据的字符串,format表示数据的格式,....后面用来接变量

char *形变量要指定长度

代码示例

从文本中读取数据

while (!ifs.eof())
{
	string line;
	getline(ifs,line);
	if (line == "")
		break;
	int ret = sscanf_s(line.c_str(), "no:%d\t\t\tname:%s\t\t\tpost:%s", &no, name, sizeof(name), post, sizeof(post));
	if (ret <= 0)
	{
		cout << "员工数据库格式匹配失败." << endl;
		exit(1);
	}
	Staff* tmp;
	if (!strcmp(post, "普通员工"))
	{
		tmp = new Employee(name);
	}
	else if (!strcmp(post, "经理"))
	{
		tmp = new Manager(name);
	}
	else if (!strcmp(post, "老板"))
	{
		tmp = new Boss(name);
	}
	s_v.push_back(tmp);
	
	
}

格式化读取成员变量的值

int ret = sscanf_s(line.c_str(), "no:%d\t\t\tname:%s\t\t\tpost:%s", &no, name, sizeof(name), post, sizeof(post));
if (ret <= 0)
{
	cout << "员工数据库格式匹配失败." << endl;
	exit(1);
}

lambda表达式


http://www.kler.cn/a/372239.html

相关文章:

  • JavaScript 中如何识别异步函数?
  • (二 上)VB 2010 设计初步
  • Kubernetes:(四)kubectl命令
  • Ajax:表单 模板引擎
  • 基于uniapp微信小程序的旅游系统
  • postgresql增量备份系列一
  • Golang Agent 可观测性的全面升级与新特性介绍
  • 记MySQL下一次DEPENDENT SUBQUERY的优化
  • Github 2024-10-29Python开源项目日报 Top10
  • 算法刷题-小猫爬山
  • 从0开始搭建一个生产级SpringBoot2.0.X项目(二)SpringBoot应用连接数据库集成mybatis-plus
  • ElasticSearch - Bucket Script 使用指南
  • 三菱FX5U PLC使用SD存储卡固件更新的方法
  • python实现excel数据导入数据库
  • 频率限制:WAF保护网站免受恶意攻击的关键功能
  • Yolov5网络架构分析以及训练图解
  • Kafka如何控制消费的位置?
  • 数据驱动业务中的BDS对账班牛返款表集成方案
  • 前端开发设计模式——观察者模式
  • 论文题目:深度学习在自然语言处理中的应用研究
  • 开源FluentFTP实操,操控FTP文件
  • SpringBoot最佳实践之 - 使用AOP记录操作日志
  • 电子电气架构 --- 汽车以太网概述
  • 论文翻译:ICLR 2024.DETECTING PRETRAINING DATA FROM LARGE LANGUAGE MODELS
  • 免费PDF页面提取小工具
  • 《自动驾驶技术的深度思考:安全与伦理的挑战》