C++查漏补缺笔记(一)

C++查漏补缺笔记(一)

Q1:构造函数的深拷贝与浅拷贝

A1:类中包含指针成员(char*等)时,务必要编写复制构造函数(形参为常引用对象,一定要是常引用才能避免浅复制)和复制赋值运算符(否则实例化a=实例化b时会发生浅复制)。浅复制的原因在于对象按值传递时,编译器会执行二进制复制,对于指针成员,形参对象会照搬其内容(即指向同一内存单元)。如果该类需要在堆区开辟空间(使用new),那么势必要在析构函数中将申请的空间delete掉。浅复制就会发生在此刻:形参在函数结束后不复存在,它会调用delete释放所new的空间;而值传递时形参对象与实参对象里的指针成员内容完全一致,导致实参在需要调用析构函数时无法释放空间。

穿插下复制构造函数的三个调用情况:
1).使用一个已经创建完毕的对象来初始化一个新对象;

Person p1(20);
Person p2(p1);

2).值传递方式给函数参数传值;
3).以值方式返回局部对象(此时会复制局部对象)

Q2: 移动构造函数与移动赋值运算符

A2:场合a)如果一个函数需要频繁调用复制构造函数,比如按值返回一个类对象时,会调用复制构造函数;比如形参为类对象时会调用复制构造函数;且如果要复制的是很大的动态对象数组,严格的调用复制构造函数会降低性能。此时应编写移动构造函数避免深复制。
场合b)在需要创建临时右值(如 对象X=对象A+对象B+对象C )时,C++编译器将使用移动构造函数和移动赋值运算符(而非复制构造函数和复制赋值运算符)以将资源从给源移动到目的地
1).移动构造函数和移动赋值运算符的参数为Myclass&&,且不能用const限定
2).重载移动构造函数和移动赋值运算符时,返回类型没变

Q3:不允许复制和赋值的类

A3:要禁止类对象(如:President Lincoln;)被复制(如:CosplayaPresident(Lincoln);//通过值传递创建一个副本),可以声明一个私有的复制构造函数
或者禁止类对象被赋值(如:President clone;clone=Lincoln;//通过赋值创建副本),可声明一个私有的赋值运算符
故解决方案如下:

class President{
private:
	President(const President&);//私有复制构造函数
	President& operator=(const President&);//私有赋值运算符
}

无需提供实现,仅仅是声明为私有即可

Q4:只允许一个实例的单例类

A4:前面的President类通过私有化复制构造函数和赋值赋值运算符可确保每个对象不可复制;但如果需要确保有且只有一个President类的对象存在(即存在President Lincoln;后禁止其他President对象存在),可使用单例概念:借助私有构造函数、私有赋值运算符和静态实例成员(关键字static)
穿插下关键字static用法:将关键字static用于函数中声明的局部变量时,该变量将成为静态局部变量;将static用于类的数据成员时,该数据成员将在所有实例间共享(类内声明,类外初始化);将static用于成员函数(方法)时,该方法将在所有成员间共享(静态成员函数只能访问静态成员变量,访问方式为1.通过创建对象然后调用;2.直接通过类名调用)。

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

class President {
private:
	string name;
	President() {};//私有构造函数,防止外部创建
	President(const President&) {};//私有复制构造函数,防止拷贝行为
	const President& operator=(const President&);//私有重载赋值运算符,防止赋值

public:
	static President& GetInstance() {
		static President OnlyInstance;//静态变量只执行一次
		return OnlyInstance;
	}

	string GetName() {
		return name;
	}
	void SetName(string InputName) {
		name = InputName;
	}
};

int main() {
	//会调用复制构造函数,然而不可访问,所以不能是值传递
	/*President OnlyPresident = President::GetInstance();*/
	President& OnlyPresident = President::GetInstance();
	OnlyPresident.SetName("A.Lincoln");

	//尝试在栈上构造,然而不可访问构造函数
	//President Trump;

	//尝试在堆中构造,然而不可访问构造函数
	//President* faker = new President();

	//不可复制
	//President Obma = OnlyPresident;

	//不可赋值
	//OnlyPresident = President::GetInstance();

	cout << "The name of President is:";
	cout << President::GetInstance().GetName() << endl;

	return 0;
}

Q5: 关键字static

A5:
1)将关键字static用于函数中声明的局部变量时,该变量将成为静态局部变量。静态局部变量的值在函数调用结束后不消失而继续保留原值,在下一次函数调用时将继续使用该(上次调用后)保留的值。静态局部变量属于静态存储类别,在静态存储区分配内存单元,在整个程序运行期间都不释放(对应的动态局部变量属于动态存储类别,占动态存储空间,函数调用后立即释放)。
2)对静态局部变量是在编译时赋初值的,即赋初值只有一次,程序运行时它已有初值,故以后每次调用函数时都保留上次函数调用结束时的值(自动变量在函数调用时赋初值)。
3)如果定义静态局部变量不赋初值,编译时自动赋初值0(数值型变量)或空字符(字符型变量),而对自动变量来说,如果不赋初值则值不定(每次函数调用结束后存储单元已经释放,下次调用时又重新另分配存储单元,而所分配的单元中的值是不可知的)。
4)定义外部变量时加一个static声明可以确保该变量只在本文件中使用–静态外部变量
5)静态成员变量:a)所有对象共享同一份数据;b)编译阶段分配内存;c)类内申明类外初始化
6)静态成员函数:a)所有对象共享同一个函数;b)静态成员函数只能访问静态成员变量

Q6: 禁止在栈中实例化的类

A6:如果需要禁止在栈上实例化类(如庞大的数据库类),而只允许在堆上实例化,需要将析构函数声明为私有的。需要注意的是,在堆中实例化后由于不能调用析构函数,因此也不能调用delete,这会导致内存泄漏。解决:需要在该类中提供一个销毁实例的静态公有函数(作为类成员,它能调用析构函数,delete内存,但形参需为类指针)

Q7:(一个)类对象作为(另一个)类的成员时的构造析构顺序

A7:当类中成员为其他类对象时,我们称该成员为对象成员。
构造顺序是,先调用对象成员的构造,再调用本类构造;析构顺序与构造熟系数相反

Q8:this指针的用途

A8:1)当形参和成员变量同名时,可用this指针加以区分(还有就是成员变量命名规范化);
2)this指针隐藏在每一个成员函数内部,指向调用该成员函数的对象;
3)*this指向对象本体,配合返回类的引用可以实现链式编程思想
4)空指针调用成员函数时如果用到this指针,需加以判断保证代码健壮性

#include<iostream>
using namespace std;

class Person {
public:
	Person(int age) {
		//1.形参与成员变量不慎重名时,用this指针区分
		this->age = age;
	}

	Person& AddAge(Person p) {//注意这里是返回对象的引用而非值(返回的是类本体);
		//Person AddAge(Person p);如果这么写返回时会调用拷贝构造函数
		this->age += p.age;
		return *this;//返回对象本身,然后即可链式调用
	}

	int age;
};

void test() {
	Person p1(10);
	cout << "p1的初始年龄为 " << p1.age << endl;
	p1.AddAge(p1).AddAge(p1).AddAge(p1);
	cout << "三次链式调用后p1 的年龄为 " << p1.age << endl;
}

int main() {
	test();

	return 0;
}

Q9:常对象与常成员函数

A9:1.还是归于this指针本是指针常量const)----指针的指向一旦指定就不可以修改----类名 const this;
2.常成员函数:在成员函数后面加const,修饰的是this指向(const 类名* const this;)----让指针指向的值也不可以修改
es:如果要在常函数或者常对象中修改值,加关键字mutable
3.常对象只能调用常函数,不可以调用普通成员函数(普通成员函数可以修改成员属性)

Q10:友元函数的三种实现

A10:友元函数的目的是让一个函数或者类访问另一个类中的私有成员,关键字friend;注意在对应类中的friend函数/类声明
1).全局函数作为friend函数
2).类作为另一个类的friend类
3).一个类的成员函数作为(另一个类的)friend

Q11:重载左移运算符<<

A11:要想实现cout<<对象,且cout在左侧,只能利用全局函数重载左移运算符;要想实现<<的链式输出,运算符重载时需要返回ostream&(对象cout属于ostream类,形参中要用&保证全局只有一个);配合友元可以实现输出自定义数据类型。

Q12:重载自增自减运算符时返回引用还是返回值

A12:先说结论:前置递增返回引用(形参列表为空),后置递增返回值(形参列表不为空)

Q13:重载赋值运算符注意事项

A13:1)检查源和目标是否属于同一个对象且目标中的指针是否为空指针,如果是就直接返回自身;
2)检查目标是否占用堆中内存,如果是就手动释放;
3)深拷贝,为目标重新在堆区申请空间以复制源在堆中内存;
4)返回对象本身(引用!)

Q14:继承中子类与父类有同名成员函数和属性时的访问

A14:1)子类对象可以直接访问到子类中同名成员(默认访问);
2)子类对象加作用域可以访问到父类同名成员;
3)当子类与父类拥有同名的成员函数,子类会隐藏父类中的同名成员函数,.加作用域可以访问父类中同名函数

Q15:写文本文件的过程

A15:1)包含头文件
#instream
2)创建流对象
ofstream ofs;
3)打开文件
ofs.open(“文件路径”,打开方式);
4)写数据
ofs<<“写入的数据”;
5)关闭文件
ofs.close();

#include<iostream>
using namespace std;

#include<fstream>//1.包含头文件
#include<string>

class PersonalData {
private:
	string m_name;
	int m_age;
	char m_sexual;
	int m_phonenumber;
public:
	//Constructor
	PersonalData(string name,int age,char sexual,int phonenumber):
		m_name(name),m_age(age),m_sexual(sexual),m_phonenumber(phonenumber){}
	//CopyConstructor
	PersonalData(const PersonalData& Person) {
		m_name = Person.m_name;
		m_age = Person.m_age;
		m_sexual = Person.m_sexual;
		m_phonenumber = Person.m_phonenumber;
	}
	//Destructor
	~PersonalData(){}

	 string ReturnName() const { return m_name; }
	 int ReturnAge() const { return m_age; }
	 char ReturnSex()const { return m_sexual; }
	 int ReturnPhNum()const { return m_phonenumber; }
};

void test01(const PersonalData& Human) {
	ofstream ofs;//2.创建流对象

	ofs.open("text01.txt", ios::out);//3.打开文件
	
	ofs << "姓名:" << Human.ReturnName() << endl;//4.写文件
	ofs << "年龄:" << Human.ReturnAge() << endl;
	ofs << "性别:" << Human.ReturnSex() << endl;
	ofs << "电话:" << Human.ReturnPhNum() << endl;
	/*ofs << 1 << endl;*/

	ofs.close();//5.写完后关闭文件
}

int main() {
	PersonalData QQh("QQh", 27, 'm', 1267);
	test01(QQh);

	system("pause");
	return 0;
}


PS1:常用文件打开方式
1)ios::in---------为读文件而打开文件
2)ios::out--------为写文件而打开文件
3)ios::ate--------初始位置:文件尾(append)
4)ios::app--------追加方式写文件
5)ios::trunc--------如果文件存在先删除,再创建
6)ios::binary-------二进制方式操作文件
文件打开方式可以用位或|操作符配合使用:如用二进制方式并且写文件,ios::out | ios::binary

Q16:读文本文件的过程

A16:1)包含头文件#instream
2)创建流对象ifsteam ifs;
3)打开文件 并且判断是否打开成功
ifs.open(“文件路径”,ios::in);
if(!ifs.is_open()){return;}
4)读数据
5)关闭文件

#include<iostream>
using namespace std;
#include<fstream>//1.包含头文件fstream
#include<string>

class PersonalData {
private:
	string m_name;
	int m_age;
	char m_sexual;
	int m_phonenumber;
public:
	//Constructor
	PersonalData(string name, int age, char sexual, int phonenumber) :
		m_name(name), m_age(age), m_sexual(sexual), m_phonenumber(phonenumber) {}
	//CopyConstructor
	PersonalData(const PersonalData& Person) {
		m_name = Person.m_name;
		m_age = Person.m_age;
		m_sexual = Person.m_sexual;
		m_phonenumber = Person.m_phonenumber;
	}
	//Destructor
	~PersonalData() {}

	string ReturnName() const { return m_name; }
	int ReturnAge() const { return m_age; }
	char ReturnSex()const { return m_sexual; }
	int ReturnPhNum()const { return m_phonenumber; }
};

void test_Read() {
	ifstream ifs;//2.创建流对象
	ifs.open("text01.txt", ios::in);//3.打开文件并且
	if (!ifs.is_open()) {//检查是否打开成功
		cout << "文件打开失败!" << endl;
		return;
	}
	//4.读数据
	//第一种,创建一个字符数组并将所有数据存入数组
	/*
	char buf[1024] = { 0 };
	while (ifs >> buf) {
		cout << buf << endl;
	}
	*/
	//第二种,创建一个字符数组并利用ifstream::getline成员函数获取数据,并存入数组
	/*
	char buf[1024] = { 0 };
	while (ifs.getline(buf, sizeof(buf))) {
		cout << buf << endl;
	}
	*/
	//第三种,放入string ,利用getline全局函数
	
	string buf;
	while (getline(ifs, buf)) {
		cout << buf << endl;
	}
	
	//第四种,利用ifstream::get()一个个字符读
	//char c;
	//while ((c = ifs.get() != EOF)) {//EOF:end of file
	//	cout << c;//这里输出有问题,是方框,其他三种都没问题??
	//}

	ifs.close();//5.关文件
}
int main() {
	test_Read();
	system("pause");
	return 0;
}


Q17:二进制方式读写文件

A17:1)二进制方式读文件:
2)二进制方式写文件:主要利用流对象调用成员函数read
istream& read(char *buffer,int len);
其中字符指针buffer指向内存中一段存储空间,len是读写的字节数

Q18:普通函数与函数模板的区别

A18:1)普通函数调用时可以发生自动类型转换(隐式)
2)函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换(类模板没有自动类型推导的使用方式!
3)如果利用显式指定类型的方式,可以发生隐式类型转换(类模板使用只能显式指定类型!
基于以上区别,1)如果函数模板和普通函数都可以实现,优先调用普通函数;2)可以通过空模板参数列表来强制调用函数模板;3)函数模板也可以发生重载;4)如果函数模板可以产生更好的匹配,优先调用函数模板;5)类模板在模板参数列表中可以有默认参数;6)类模板中的成员函数在调用时才创建(普通类中的成员函数一开始就创建)

Q19:类模板对象做函数参数

A19:类模板实例化出的对象,有三种向函数传参的方式:
1)指定形参传入类型----直接显示对象的数据类型(使用最广泛)
2)(形参)参数模板化----将对象中的参数变为模板进行传递
3)整个类模板化----将这个对象类型 模板化进行传递

Q20:类模板分文件编写

A20:类模板的声明与实现写在一个文件下,且后缀名改为.hpp,使用时包含之
类模板只在调用时被创建,分文件编写时会出现链接不到的问题,所以如果声明与实现分别写在两个文件.cpp和.h,则使用时包含.cpp文件)

Q21:内建函数对象-算数仿函数、关系仿函数和逻辑仿函数

A21:使用时要引入头文件

#include<functional>
//算数仿函数
template<class T> T plus<T>//加法仿函数
template<class T> T minus<T>//减法仿函数
template<class T> T multiplies<T>//乘法仿函数
template<class T> T divides<T>//除法仿函数
template<class T> T modulus<T>//取模仿函数
template<class T> T negate<T>//取反仿函数
//关系仿函数
template<class T> bool equal_to<T>//等于
template<class T> bool not_equal_to<T>//不等于
template<class T> bool greater<T>//大于
template<class T> bool greater_equal<T>//大于等于
template<class T> bool less<T>//小于
template<class T> bool less_equal<T>//小于等于
//逻辑仿函数
template<class T> bool logical_and<T>//逻辑与
template<class T> bool logical_or<T>//逻辑或
template<class T> bool logical_not<T>//逻辑非
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值