Essencial C++ 第四章学习记录,头文件,默认赋值函数和拷贝构造的问题,const,this指针,static,类模板

本章知识点总结:

1. 我们在定义一个类时,可以把关于类的声明写入一个头文件中,然后具体的实现过程需要在单独的cpp文件中完成,需要包含刚才定义的头文件,当使用头文件中声明的函数名或者变量时要加入类名作用域,这是目前比较标准的写法。然后如果我们在声明一个类的时候,同时定义了成员函数,那么编译器会自动将其视为  inline。  但是如果我们按照标准写法去实现成员函数时,如果需要指明为 inline 那么就需要我们去自己手写

2. 构造函数作用,初始化成员变量,可以通过在构造函数的代码段内实现,也可以通过初始化列表进行初始化,与之对应的是虚构函数,虚构函数的写法是在类名前写一个  ~ , 析构函数不接受任何参数和返回值,用来对之前申请的资源进行释放

3.当一个空类被创建时,编译器会默认帮我们创建构造函数,析构函数,赋值函数和拷贝构造函数,其中赋值函数和拷贝构造函数会存在一些问题,下面通过简单的示例进行讲解

在下面这个例子中设计的注意点:1.  对输入输出运算符进行重载时,一定是类外重载,因为 << 运算符的左侧一定是一个ostream对象,右侧一定是一个类,但是如果我们进行类内重载的话,参数列表中默认的最左侧参数是我们的this指针,这样就不符合我们的实际使用情况了   2. 下面这个程序会崩溃,不管是Cstu st2(st1); 还是Cstu st2 = st1;都会崩溃,这就是上面提到的关于默认的赋值函数和拷贝构造函数可能出现的问题,由于类中的成员变量是在堆区进行申请空间,然后通过默认的上述两个函数对st2对象进行初始化的时候,只是单纯的st2.age = st1.age    st2.matric = st1.matric 这种操作,会导致st1 和st2的成员变量指向同一块儿空间,然后我们在析构函数中对这块儿空间进行释放,所以当st2所在的析构函数被调用后,空间被释放,但此时st1会试图操控被释放的空间从而引发错误

#include"stdafx.h"
#include<iostream>

using namespace std;

class Cstu {
private:
	int age;
	int matric[2] = { 0 };

public:
	Cstu(int age) {
		this->age = age;
	}

	~Cstu()
	{
		delete[]this->matric;
	}
	friend ostream& operator << (ostream &os, const Cstu &st);
};

// 不进行修改加上const  当返回值为void时,无法进行连续输出
ostream& operator << (ostream &os, const Cstu &st) {
	os << st.age<<" "<<st.matric[0]<<" "<<st.matric[1];
	return os; 
}

int main(void) {

	Cstu st1(12);
	{
		//Cstu st2(st1);
		Cstu st2 = st1;
		cout << st2<<endl;
	}
	cout << st1 ;
	return 0;
}

 

4. const 和 mutable: 关于const变量的性质大家可以参考https://www.cnblogs.com/xudong-bupt/p/3509567.html

对其中的代码进行解释

#include<iostream>
using namespace std;
int main(){
    int a1=3;   ///non-const data
    const int a2=a1;    

    int * a3 = &a1;   
    
    //a4 是一个指针型变量,指向const的int型数据,所以指针可指向不同空间,但是数据不能改变
    const int * a4 = &a1; 
    //对于上一行来说  a4=&a2是正确的,即只是改变指针的指向,但是*a4=5是错的,因为这是在修改数据

    //a5 是一个const型的指针变量,指向int数据,所以在指针指向不能更改,但是数据可以更改
    //*a5=2 正确   a5=&a2错误
    int * const a5 = &a1;  

    //解读如上,a6 a7 都是一个const型的指针变量,均指向const的int型数据
    int const * const a6 = &a1;  
    const int * const a7 = &a1;  

    return 0;
}

由于在const修饰的成员函数中不能修改任何成员变量,但是如果我们在成员变量的定义前加上mutable 表示对该变量所做的修改不会破坏类的常量性

5. this指针 第一部分,用来只用函数的调用者,在内部工作过程中,编译器自动将this指针加到每一个成员函数的参数列表的最左侧,这也就是为什么我们上面重载 << 的时候要放到类外去进行

6. 静态成员 static,该类型变量是依托于类存在的,并不是依托于对象,所以所有对象均使用同一份static变量,且static变量一定得去类外初始化,如果一定要在类内初始化的话,应该声明为const。如果用static去修饰成员函数,则该成员函数可通类名作用域进行调用,然后需要注意一点就是,在静态的成员函数中是不能使用非静态的成员变量的,因为非静态成员变量是依托于对象存在的,我们用类名去调用静态成员函数,哪里来的对象嘛。

7.在使用函数指针的时候,如果是普通的函数指针,那么函数名即表示地址,如果是类中的成员函数,则需要用 &来表示地址

8.关于类模板的注意事项

#include "stdafx.h"
#include<iostream>
using namespace std;

template<typename elemType>
class Cstu {
public:
	Cstu(elemType elem):a(elem){}
	void myshow();
private:
	mutable elemType a;
};

//之后我们在Cstu类外,只要遇到类名Cstu,都需要写<> 
//同时template<typename elemType>的作用域只存在于紧挨着的代码段
template<typename elemType>
void Cstu<elemType>::myshow(){
	cout << (*this).a << endl;
}


int main() {
	Cstu<int> stu(5);
	stu.myshow();
	return 0;
}

9. this指针:(1)是指向当前对象的指针 以上面的代码为例就是 Cstu* this ,this是一个指针变量,指向Cstu  所以this实际存储的是一个对象的地址,也就是说只要拿到了对象的地址,就可以也是用 -> 这个符号进行函数调用。同理和普通指针类似,*this 即代表对象本身(2)this在成员函数的开始执行前构造,在成员的执行结束后清除。并且this不是成员变量,我们不能在主函数中访问this指针 (3)为什么我们能在非静态成员函数内部使用this指针呢,是因为编译器默认帮我们把this指针当做参数列表中的第一个参数进行传入,所以我们才能用  通过代码进行说明

#include "stdafx.h"
#include<iostream>
using namespace std;

class Cstu {
public:
	Cstu(int elem=5):a(elem){}
	Cstu* myAdd() {
		return this;
	}
	void showA() {
		cout << this->a << " " << (*this).a << endl;
	}
private:
	int a;
};


int main() {
	Cstu stu(5);
	Cstu *stu2 = stu.myAdd();
	stu2->showA();
	return 0;
}





 

 

4.1 实现一个类

知识点总结:在头文件中进行类或者函数的声明,在源文件中进行具体实现。一般而言,类由两部分组成,一组公开的操作函数和重载的运算符,另一组是私有的实现细节。这些操作函数和运算符成为类的成员函数,并代表这个类的公开接口。 另外需要注意的是,按照规范定义一般在单独的源文件中进行,如果把定义和声明写在了一起,编译器会将其自动视为内联函数。关于书中p103的题目,我的做法如下

//Stack.h
#pragma once
#include"stdafx.h"
#include<string>
#include<vector>

using namespace std;
class Stack {
public:
	bool push(const string&);
	bool pop(string &);
	bool peek(string &);

	bool empty();
	bool full();

	bool find(const string&);
	int count(const string&);

	int size() {
		return _stack.size();
	}
private:
	vector<string> _stack;
};
//Stack.cpp
#include"stdafx.h"  一定要写在第一行!!!!!
#include<iostream>
#include"Stack.h"
#include<vector>
#include<string>
#include<algorithm>

using namespace std;

 bool Stack::empty(){
	 return _stack.empty();
}

 bool Stack::pop(string &elem) {
	 if (empty()) {
		 return false;
	 }
	 elem = _stack.back();
	 _stack.pop_back();
	 return true;
 }

 inline bool Stack::full() {
	 return _stack.size() == _stack.max_size();
 }

 bool Stack::peek(string &elem) {
	 if (empty()) {
		 return false;
	 }
	 elem = _stack.back();
	 return true;
 }

 bool Stack::push(const string &elem) {
	 if (full()) {
		 return false;
	 }
	 _stack.push_back(elem);
	 return true;
 }

 bool Stack::find(const string &elem) {
	 vector<string>::iterator first = _stack.begin();
	 vector<string>::iterator last = _stack.end();
	 for (;first != last;first++) {
		 if (*first == elem) {
			 return true;
		 }
	 }
	 return false;
 }

 int Stack::count(const string &elem) {
	 int count = 0;
	 if (!find(elem)) {
		 return -1;
	 }
	 else {
		 vector<string>::iterator first = _stack.begin();
		 vector<string>::iterator last = _stack.end();
		 for (;first != last;first++) {
			 if (*first == elem) {
				 count++;
			 }
		 }
	 }

	 return count;
 }
// chapter01.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include<iostream>
#include<vector>
#include"Stack.h"
using namespace std;

int main() {
	
	string str("messi"),str2;
	Stack s;
	s.push(str);
	s.push("ronaldo");
	s.push("ronaldo");
	s.push("xavi");
	s.push("xavi");
	s.push("xavi");

	cout << "s size:" << s.size()<<endl;

	s.peek(str2);
	cout << "s peek:" << str2 <<endl;

	s.pop(str2);
	cout << "s size:" << s.size() <<endl;

	cout << "s empty:" << s.empty()<<endl;
	cout << "s full:" << s.full()<<endl;

	cout << "s find:" << s.find("xavi") << endl;
	cout << "s find:" << s.find("kaka") << endl;;

	cout << "s count" << s.count("xavi") << endl;
	cout << "s count" << s.count("kaka") << endl;
	return 0;
}



以上结果完全正确,书中的答案给的是把定义和声明都写在了头文件中,但是我把他们分开了.

这里还涉及到关于关键字extern的知识,extern的作用最精髓的就是,在局部范围声明出一个全局变量。

https://www.cnblogs.com/mch0dm1n/p/5727667.html

个人见解:

extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。

extern 变量
在一个源文件里定义了一个数组:char a[6]; 在另外一个文件里用下列语句进行了声明:extern char *a; 请问,这样可以吗? 答案与分析: 1)、不可以,程序运行时会告诉你非法访问。原因在于,指向类型T的指针并不等价于类型T的数组。extern char *a声明的是一个指针变量而不是字符数组,因此与实际的定义不同,从而造成运行时非法访问。应该将声明改为extern char a[ ]。 2)、例子分析如下,如果a[] = "abcd",则外部变量a=0x12345678 (数组的起始地址),而*a是重新定义了一个指针变量a的地址可能是0x87654321,直接使用*a是错误的. 3)、这提示我们,在使用extern时候要严格对应声明时的格式,在实际编程中,这样的错误屡见不鲜。 4)、extern用在变量声明中常常有这样一个作用,你在*.c文件中声明了一个全局的变量,这个全局的变量如果要被引用,就放在*.h中并用extern来声明。

函数
extern 函数1 常常见extern放在函数的前面成为函数声明的一部分,那么,C语言的关键字extern在函数的声明中起什么作用? 答案与分析: 如果函数的声明中带有关键字extern,仅仅是暗示这个函数可能在别的源文件里定义,没有其它作用。即下述两个函数声明没有明显的区别: extern int f(); 和int f(); 当然,这样的用处还是有的,就是在程序中取代include “*.h”来声明函数,在一些复杂的项目中,我比较习惯在所有的函数声明前添加extern修饰。 extern 函数2 当函数提供方单方面修改函数原型时,如果使用方不知情继续沿用原来的extern申明,这样编译时编译器不会报错。但是在运行过程中,因为少了或者多了输入参数,往往会照成系统错误,这种情况应该如何解决? 答案与分析: 目前业界针对这种情况的处理没有一个很完美的方案,通常的做法是提供方在自己的xxx_pub.h中提供对外部接口的声明,然后调用方include该头文件,从而省去extern这一步。以避免这种错误。

习题4.3

// chapter01.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include<string>
#include<iostream>

using namespace std;

class globalWrapper {

public:
	static void put_program_name(string &name) {
		program_name = name;
	}
	static void put_version_stamp(string &stamp) {
		version_stamp = stamp;
	}
	static void put_version_number(int number) {
		version_number = number;
	}
	static void put_tests_run(int run) {
		tests_run = run;
	}
	static void put_tests_passed(int passed) {
		tests_passed = passed;
	}

	static string get_program_name() {
		return program_name;
	}
	static string get_version_stamp() {
		return version_stamp;
	}
	static int get_version_number() {
		int version_number;
	}
	static int get_tests_run() {
		return tests_run;
	}
	static int get_tests_passed() {
		return tests_passed;
	}

private:
	static string program_name;
	static string version_stamp;
	static int version_number;
	static int tests_run;
	static int tests_passed;

};

string globalWrapper::program_name ;
string globalWrapper::version_stamp;
int globalWrapper::version_number;
int globalWrapper::tests_run;
int globalWrapper::tests_passed;

int main() {
	//string s("ww");
	//globalWrapper::put_program_name(s);
	cout << globalWrapper::get_program_name();
	return 0;
}



 习题4.4

// chapter01.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include<iostream>
#include<string>
#include<map>
#include<ctime>
using namespace std;


class UserProfile {
public:
	enum uLevel
	{
		Beginner,
		Intermediate,
		Advanced,
		Guru
	};

	UserProfile();
	UserProfile(string login, uLevel = Beginner);

	bool operator ==(const UserProfile &);
	bool operator !=(const UserProfile &);

	//以下函数用来读取数据
	string login() const { return _login; }
	string user_name() const { return _user_name; }
	int login_count() const { return _time_logged; }
	int guess_count() const { return _guesses; }
	int guess_correct() const { return _correct_guesses; }
	double guess_average() const;
	string level() const;

	//以下函数用来写入数据
	void reset_login(const string&val) { _login = val; }
	void user_name(const string &val) { _user_name = val; }

	void reset_level(const string&);
	void reset_level(uLevel newlevel) { _user_level = newlevel; }

	void reset_login_count(int val) { _time_logged = val; }
	void reset_guess_count(int val) { _guesses = val; }
	void reset_guess_correct(int val) { _correct_guesses = val; }

	void bump_login_count(int cnt = 1) { _time_logged += cnt; }
	void bump_guess_count(int cnt = 1) { _guesses += cnt; }
	void bump_guess_correct(int cnt = 1) { _correct_guesses += cnt; }



private:
	string _login;
	string _user_name;
	int _time_logged;
	int _guesses;
	int _correct_guesses;
	uLevel _user_level;

	static map<string, uLevel> _level_map;
	static void init_level_map();
	//static string guest_login();
};


inline double UserProfile::guess_average() const {
	return _guesses ? (double)_correct_guesses / (double)_guesses * 100 : 0;
}

inline UserProfile::UserProfile(string login, uLevel level):_login(login), _user_level(level),
		_time_logged(1),_guesses(0),_correct_guesses(0){}

#include<cstdlib>

inline UserProfile::UserProfile() : _login("guest"), _user_level(Beginner),
		_time_logged(1), _guesses(0), _correct_guesses(0) {
	static int id = 0;
	char buffer[16];
	_itoa_s(id++, buffer, 10);
	_login += buffer;
}

inline bool UserProfile::operator==(const UserProfile &rhs) {
	if (_login == rhs.login() && _user_name == rhs._user_name)
		return true;
	return false;
}

inline bool UserProfile::operator!=(const UserProfile &rhs) {
	return !(*this== rhs);
}

inline string UserProfile::level()const {
	static string _level_table[] = { "Beginner","Intermediate","Advanced","Guru" };
	return _level_table[_user_level];
}

ostream & operator <<(ostream &os, const UserProfile &rhs) {
	os << rhs.login() << " " << rhs.level() << " " << rhs.login_count() << " " << rhs.guess_count() << " "
		<< rhs.guess_correct() << " " << rhs.guess_average() << "\n";
	return os;
}

map<string, UserProfile::uLevel> UserProfile::_level_map;

void UserProfile::init_level_map() {
	_level_map["Beginnner"] = Beginner;
	_level_map["Intermediate"] = Intermediate;
	_level_map["Advanced"] = Advanced;
	_level_map["Guru"] = Guru;
}

inline void UserProfile::reset_level(const string &level) {
	map<string, UserProfile::uLevel>::iterator it;
	if (_level_map.empty())
		init_level_map();
	_user_level = ((it = _level_map.find(level)) != _level_map.end()) ? it->second : Beginner;
}

istream & operator >> (istream &is, UserProfile &rhs) {
	string login, level;
	is >> login >> level;

	int lcount, gcount, gcorrect;
	is >> lcount >> gcount >> gcorrect;
	rhs.reset_login(login);
	rhs.reset_level(level);
	rhs.reset_guess_count(gcount);
	rhs.reset_guess_correct(gcorrect);
	rhs.reset_login_count(lcount);
	return is;
}

int main() {
	srand((unsigned int)(time(NULL)));
	UserProfile anon;
	cout<<anon;

	UserProfile anon_too;
	cout << anon_too;

	UserProfile anna("Annal", UserProfile::Guru);
	cout << anna;
	return 0;
}



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巴塞罗那的风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值