C++面向对象笔记

#include<iostream>
using namespace std;

/*
面向对象:
抽象   封装
面向对象的三大特征:封装  继承  多态
面向对象的四大特征:抽象  封装  继承 多态


class Person
{
构造
拷贝构造
等号运算符重载
析构

this
};
*/


/*
严格控制某一个类只产生一个对象


*/

/*
类中的static关键字

成员属性加上static代表整个类只有这一份
静态成员属性必须在类外进行初始化
私有的static成员属性依然可以在外界进行初始化
但是私有的成员除了初始化,在外界其他地方不可以访问


私有成员的访问不必依赖对象,可以直接使用  类名::



静态成员方法
使用不依赖于对象
访问可以直接使用 类名::
静态成员方法没有传入this指针

所以:静态成员方法内不可以访问普通成员
*/

/*
const 成员方法
常对象只能调用普通常方法和静态方法
常方法指的是在方法参数列表后括号后面加上const,
这个const修饰的是this指针

建议:
所有的普通成员方法,如果方法内没有修改成员,
就将该方法写成常方法


const 成员属性
const成员属性必须放在初始化列表中进行初始化



*/

/*
初始化列表
只有构造函数有初始化列表

const 成员属性必须放在初始化列表
引用 成员属性必须放在初始化列表

必须初始化的成员属性,都必须放在初始化列表



位置:在构造函数的参数列表下面,函数体的前{上面
*/


class  Stu
{
private:
	char* _name;
	const int _sex;
	int& _school;
	int _age;
	double _grade;
	static int _num;
	static Stu* p_stu;

	Stu(const char* name, int school, int sex, int age, double grade)
		:_sex(sex), _school(school)
	{
		_name = new char[strlen(name) + 1];
		strcpy_s(_name, strlen(name) + 1, name);
		//_sex = sex;
		_grade = grade;
		_age = age;
		cout << "Stu(const char* name, int sex, int age, double grade)" << endl;
	}


public:
	static Stu* get_stu(const char* name, int school, int sex, int age, double grade)
	{
		if (NULL == p_stu)
		{
			p_stu = new Stu(name, school, sex, age, grade);
		}

		return p_stu;
	}



	static int get_num()
	{
		get_age();
		return _num;
	}

	static int get_age()
	{
		cout << "hahah" << endl;
		//show();
		//return this->_age;
	}
	
	//构造
	/*
	Stu(int school):_sex(0),_age(int()),_grade(0),_school(school)
	{
		this->_name = NULL;
		//_sex = 0;
		//_age = 0;
		//_grade = 0;
		cout << "Stu()" << endl;
	}
	*/


	/*
	//拷贝构造
	Stu(const Stu& src)
		:_sex(src._sex),_school(src._school)
	{
		//防止浅拷贝
		_name = new char[strlen(src._name) + 1];
		strcpy_s(_name, strlen(src._name) + 1, src._name);

		//_sex = src._sex;
		_grade = src._grade;
		_age = src._age;
		cout << "Stu(const Stu& src)" << endl;
	}*/

	
	//等号运算符重载
	Stu& operator=(const Stu& src)
	{
		cout << "Stu& operator=(const Stu& src)" << endl;
		//防止自赋值
		if (&src == this)
		{
			return *this;
		}

		//防止内存泄漏
		if (NULL != _name)
		{
			delete[]_name;
		}

		//防止浅拷贝
		_name = new char[strlen(src._name) + 1];
		strcpy_s(_name, strlen(src._name) + 1, src._name);

		_sex = src._sex;
		_grade = src._grade;
		_age = src._age;

		return *this;
	}

	//析构
	~Stu()
	{
		cout << "~Stu()" << endl;
		if (NULL != _name)
		{
			delete[]_name;
			_name = NULL;
		}
	}

	//show
	void show()const
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
		cout << "sex:" << _sex << endl;
		cout << "grade:" << _grade << endl;
	}


	void change_grade(int flag, double grade)
	{
		if (flag == 1)
		{
			_grade = grade;
		}
	}

	double get_grade()
	{
		return _grade;
	}

};

Stu* Stu::p_stu = NULL;

int Stu::_num = 0;


/*
单例模式

1.将构造函数写在私有的里面
2.在共有的里面写一个静态的获取对象指针的方法 --- 获取的入口
3.将唯一的对象指针存储在私有的静态成员属性中,每次被获取

*/


int main()
{
	/*
	const Stu stu("zhangsan", 1, 23, 5);
	stu.show();//  const Stu  * const this      *this this->
	//const Stu* p;
	*/


	Stu* stu = Stu::get_stu("zhangsan", 2, 1, 23, 5);

	Stu* stu2 = Stu::get_stu("lisi", 2, 1, 23, 5);



#if 0
	//cout << Stu::_num << endl;
	int n = Stu::get_num();
	Stu stu;
	stu.show();
	stu.get_num();

	Stu stu;
	Stu stu1("zhansgan", 1, 34, 10);
	stu = stu1;
	Stu stu2 = stu;

	//stu.show();
	//stu1.show();
	//stu2.show();


	//cout << stu._age << endl;
	//stu._age = 10;


	/*
	访问权限

	public:  公有的
	所有的人都可以访问

	一般对外提供的接口使用public


	private:私有的
	只有类内部的成员方法可读可写
	类外不可见不可写

	除了必须对外提供的接口,其他的都写成私有
	对于外界需要访问成员属性也写成私有,在共有的权限
	中提供访问接口

	在class类中所有的成员默认的属性是私有--private

	在c++中struct也是类
	在struct类中所有成员的默认属性是公有

	如何选择使用class和struct:
	如果简单的类,成员方法不多的一般用struct
	class一般用于较大一点,功能较多的类

	*/

#endif
	return 0;
}
	// 这里是要求对象stu内的数据不允许被改变
	const Student stu("shen", 10);
	stu.show();//这里直接报错

show方法中需要的是Person* const this,此时const修饰的类型是Person*,const修饰的内容是this,表示this指向不可改变。

而实际上per传过去的指针是const Person* this,此时const修饰的类型是Person,const修饰的内容是*this,表示*this不可改变

实参是要求*this不允许改变,而形参只是保证了this指针的指向不变,此时把一个常量的地址,给了一个非常量的指针。通过形参可以获取到this指向的空间,此时泄露了常量的地址,这是不被编译器允许的。

常对象只能调用普通常方法静态方法,常方法指的是在方法参数列表后括号后面加上const,这个const修饰的是this指针。方法后加上const,表示该方法对数据成员 可读不可写

void show() const{
	cout << "name:" << _name << endl;
	cout << "age:" << _age << endl;
	cout << "sex:" << _sex << endl;
	cout << "grade:" << _grade << endl;
}

const成员属性必须初始化,利用初始化列表进行初始化,其中只有构造函数有初始化列表

Stu(int school):_sex(0),_age(int()),_grade(0){
	this->_name = NULL;
	//_sex = 0;
	//_age = 0;
	//_grade = 0;
	cout << "Stu()" << endl;
}

Stu(const Stu& src):_sex(src._sex){
	//防止浅拷贝
	_name = new char[strlen(src._name) + 1];
	strcpy_s(_name, strlen(src._name) + 1, src._name);
	//_sex = src._sex;
	_grade = src._grade;
	_age = src._age;
	cout << "Stu(const Stu& src)" << endl;
}

由于引用&本质就是指针的解引用,在使用的时候汇编代码都是直接讲引用替换成*pointer的形式,所以当一个变量成为另一个变量的解引用后,后面都不允许成为其他变量的解引用。

成员属性里面有引用&的时候,引用也必须初始化,而且也是必须使用初始化列表的。

一定弄清初始化和赋值的区别,初始化不用=符号

Stu(int school):_sex(0),_age(int()),_grade(0),_school(school){
	this->_name = NULL;
	//_sex = 0;
	//_age = 0;
	//_grade = 0;
	cout << "Stu()" << endl;
}

Stu(const Stu& src):_sex(src._sex),_school(src._school){
	//防止浅拷贝
	_name = new char[strlen(src._name) + 1];
	strcpy_s(_name, strlen(src._name) + 1, src._name);

	//_sex = src._sex;
	_grade = src._grade;
	_age = src._age;
	cout << "Stu(const Stu& src)" << endl;
}

必须初始化的数据: const数据成员,引用数据成员。都必须使用初始化列表进行初始化。

单例模式
1.将构造函数写在private里面
2.在public里面写一个静态的获取对象指针的方法 — 获取实例的入口
3.将唯一的对象指针存储在private静态成员属性中,每次被获取

限制只构造一个对象:不给外界提供构造方法的接口

class Stu{
private:
	static Stu* p_stu;
	
	Stu(const char* name, int school, int sex, int age, double grade)
		:_sex(sex), _school(school)
	{
		_name = new char[strlen(name) + 1];
		strcpy_s(_name, strlen(name) + 1, name);
		//_sex = sex;
		_grade = grade;
		_age = age;
		cout << "Stu(const char* name, int sex, int age, double grade)" << endl;
	}

publicstatic Stu* get_stu(const char* name, int school, int sex, int age, double grade)
	{
		if (NULL == p_stu)
		{
			p_stu = new Stu(name, school, sex, age, grade);
		}

		return p_stu;
	}
}


int main(){
	Stu* Stu::p_stu = NULL;
	Stu* stu = Stu::get_stu("shen",1,1,20,1);
	return 0;
}

通过链表实现一个单例模式的链栈

mlist.h

#pragma once
#ifndef LIST_H
#define LIST_H
#include<iostream>

using namespace std;

struct Node {
	int _val;
	Node* _next;
	Node(int val = int()) {
		this->_val = val;
		this->_next = NULL;
	}
};


class List {
public:
	List() {
		this->_head = new Node();
		this->_tail = this->_head;
	}

	// 拷贝构造传参必须传引用,防止死递归
	List(const List& src) {
		// 防止浅拷贝
		this->_head = new Node();
		this->_tail = this->_head;

		Node* tmp = src._head->_next;
		while (tmp != NULL) {
			this->insert_tail(tmp->_val);
			tmp = tmp->_next;
		}
	}

	List& operator=(const List& src) {
		// 防止自赋值
		if (this == &src) {
			return *this;
		}
		
		// 防止内存泄露
		while (this->is_empty()) {
			this->delete_head();
		}

		// 防止浅拷贝
		Node* tmp = src._head->_next;
		while (NULL != tmp) {
			this->insert_tail(tmp->_val);
			tmp = tmp->_next;
		}
		return *this;
	}

	~List() {
		while (!is_empty()) {
			delete_head();
		}
		delete _head;
		_head = NULL;
		_tail = NULL;
	}

	bool is_empty() const{
		return NULL == this->_head->_next;
	}

	void insert_head(int val) {
		Node* new_node = new Node(val);
		// 当前插入的是第一个结点,修改尾指针
		if (_head == _tail) {
			_tail = new_node;
		}
		new_node->_next = _head->_next;
		_head->_next = new_node;
	}

	void insert_tail(int val) {
		Node* new_node = new Node(val);
		new_node->_next = NULL;
		_tail->_next = new_node;
		_tail = new_node;
	}

	bool delete_head() {
		if (is_empty()) {
			return false;
		}
		Node* tmp = _head->_next;
		_head->_next = tmp->_next;
		
		delete tmp;
		tmp = NULL;

		// 若当前链表为空
		if (is_empty()) {
			_tail = _head;
		}
		return true;
	}

	bool delete_tail() {
		if (is_empty()) {
			return false;
		}

		Node* tmp = _head;
		while (tmp->_next != _tail) {
			tmp = tmp->_next;
		}
		tmp->_next = NULL; // 这里一定记得置空,否则遍历链表时终止条件有误

		delete _tail;
		_tail = tmp;

		return true;
	}

	Node* search(const int val) {
		Node* tmp = _head->_next;
		while (tmp != NULL && tmp->_val != val) {
			tmp = tmp->_next;
		}
		if (NULL == tmp) {
			return NULL;
		}
		return tmp;
	}

	Node* get_first() const{
		return _head->_next;
	}

	Node* get_last() const{
		return _tail;
	}

	void show() const {
		Node* tmp = _head->_next;
		while (tmp != NULL) {
			cout << tmp->_val << " ";
			tmp = tmp->_next;
		}
		cout << endl;
	}

private:
	Node* _head;
	Node* _tail;
};
#endif

mlist.cpp

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

using namespace std;

int main() {
	List list;
	for (int i = 0; i < 10; i++) {
		list.insert_head(i * 10);
	}
	list.show();

	for (int i = 0; i < 10; i++) {
		list.insert_tail(i * 10);
	}
	list.show();

	Node* first = list.get_first();
	cout << first->_val << endl;
	Node* last = list.get_last();
	cout << last->_val << endl;

	list.delete_head();
	list.show();
	list.delete_tail();
	list.show();

	return 0;
}

mstack.h

#pragma once
#ifndef MSTACK_H
#define MSTACK_H
#include"mlist.h"

class Stack {
private:
	static Stack* ptr_stack;
	List _list;
	Stack() {
		
	}

public:
	static Stack* get_stack() {
		if (NULL == ptr_stack) {
			ptr_stack = new Stack();
		}
		return ptr_stack;
	}

	// 头插
	void push(int val) {
		_list.insert_head(val);
	}

	// 头删
	void pop() {
		if (is_empty()) {
			return;
		}
		_list.delete_head();
	}

	int top() const{
		return _list.get_first()->_val;
	}

	bool is_empty() const {
		return _list.is_empty();
	}

	// const修饰函数在本函数内部不会修改类内的数据成员,不会调用其它非 const 成员函数。
	void show() const {
		_list.show();
	}
};

#endif

mstack.cpp

#include"mstack.h"

//类外进行类内静态成员初始化
Stack* Stack::ptr_stack = NULL;

int main() {
	Stack* stack = Stack::get_stack();
	for (int i = 0; i < 10; i++) {
		stack->push(i);
	}
	stack->show();
	cout<< stack->top() << endl;
	stack->pop();
	stack->show();
	return 0;
}

类的编译顺序:先编译类名、再编译成员名、再编译成员方法体

对象生成顺序:先生成成员对象、再生成自身对象

对象析构顺序:先析构自身、再析构成员

成员对象如果没有默认的构造函数,该成员对象的构造必须手动放在初始化列表

如果成员对象有默认的构造函数,系统会自动在初始化列表加上该对象的默认构造

Head(int color):_eye(color), _nose(){
	_eye._color = color;
	show();
}

临时对象的生命周期只在本行

#include<iostream>

using namespace std;

class Tmp
{
public:
	Tmp()
	{
		cout << "Tmp()" << endl;
	}
	Tmp(int a)
	{
		cout << "Tmp(int a)" << endl;
	}
	Tmp(const Tmp& src)
	{
		cout << "Tmp(const Tmp& src)" << endl;
	};
	Tmp& operator=(const Tmp& src)
	{
		cout << "Tmp& operator=(const Tmp& src)" << endl;
		return *this;
	}
	~Tmp()
	{
		cout << "~Tmp()" << endl;
	}
};

int main() {
	Tmp t;
	t = 20;
	return 0;
}

在这里插入图片描述
先构造临时对象—>使用临时对象进行拷贝构造目标对象—>析构临时对象
以上三个步骤会被编译器直接优化为:构造目标对象

	Tmp t = 20; //优化后等价于 Tmp t(20) 

在这里插入图片描述
临时对象有常属性,是const的
在这里插入图片描述

第三句在理论上需要做:用10构造临时对象,把临时对象的引用给到t3,这时就把常对象的地址泄露给了非常量的指针,所以报错。第二句可以正常执行。
在这里插入图片描述
像这样加上const就不报错了
在这里插入图片描述
引用&就是 指向临时对象的地址,可以看到const Tmp& t3 = 10这一行没有进行析构,如果对临时对象进行引用,该临时对象的生命周期会扩大为引用对象析构时间。如果临时对象析构了,那t3引用也就没有意义了。

#include<iostream>

using namespace std;

class Tmp
{
public:
	Tmp()
	{
		cout << "Tmp()" << endl;
	}
	Tmp(int a)
	{
		_a = a;
		cout << "Tmp(int a)" << endl;
	}
	Tmp(const Tmp& src)
	{
		cout << "Tmp(const Tmp& src)" << endl;
	};
	Tmp& operator=(const Tmp& src)
	{
		cout << "Tmp& operator=(const Tmp& src)" << endl;
		return *this;
	}
	~Tmp()
	{
		cout << "~Tmp()" << endl;
	}
	int _a;
};

const Tmp& fun(){
	return 10;
}

int main() {
	const Tmp& t = fun();
	cout << endl;
	cout << t._a << endl;
	return 0;
}

在这里插入图片描述

先根据10构造临时对象,该临时对象分配在fun()栈帧上,fun()结束后,临时对象的空间也被销毁(销毁指的是标记为未用空间),引用t成为了野指针。所以最后t._a是随机值。

总结: 禁止返回临时对象 、局部对象的引用或者指针

#include<iostream>

using namespace std;

class Tmp
{
public:
	Tmp()
	{
		cout << "Tmp()" << endl;
	}
	Tmp(int a)
	{
		// _a = a;
		cout << "Tmp(int a)" << endl;
	}
	Tmp(const Tmp& src)
	{
		cout << "Tmp(const Tmp& src)" << endl;
	};
	Tmp& operator=(const Tmp& src)
	{
		cout << "Tmp& operator=(const Tmp& src)" << endl;
		return *this;
	}
	~Tmp()
	{
		_a = 111;
		cout << "~Tmp()" << endl;
	}
	int _a;
};

Tmp fun(Tmp tt) {
	Tmp t(10);
	/*
	  构造t
	*/
	return t;
	/*
	用t拷贝构造临时对象
	析构t
	*/
}

int main() {
	Tmp t1;
	Tmp t2;
	t1 = fun(t2);
	return 0;
}

在这里插入图片描述
以上代码执行过程太复杂,需要优化。

建议:

  1. 函数传递参数,能传引用就传引用。如果确定函数中不会修改参数的值,就使用const引用
  2. 如果要返回的数据是一个新产生的对象,可以返回临时量就返回临时量,并且使用返回的临时量直接构造新的对象。

优化为如下形式:
在这里插入图片描述
堆区申请对象

int main() {
	// 没有析构,作用域结束才会释放空间。需要手动调用delete析构
	Tmp* tp = new Tmp(); 
	return 0;
}

关于初始化的顺序

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

using namespace std;

class Test {
public:
	Test(int data = 10):b(data), a(b) {
		cout << "Test(int data)" << endl;
	}
	void show() {
		cout << a << " " << b << endl;
	}
private:
	int a;
	int b;
};

int main()
{
	Test t;
	t.show();
	return 0;
}

在这里插入图片描述
成员初始化列表中,谁在内存前面,谁先初始化。这里是ma先初始化,然后mb初始化

调用析构函数的以后,对象只是在逻辑上没有了。如果析构函数里面没有调用delete,此时内存上是还有数据的,只有当前函数结束,栈帧回退的时候内存才被回收。

拷贝构造: 此函数如果不进行重写,默认是一个浅拷贝。包括memcpy和realloc都是浅拷贝。

赋值运算符: 如果不重写赋值运算符,默认也是一个浅拷贝

常对象不能调用普通方法: 普通方法的形参为普通指针,而实参为常量,不能泄露地址给不同指针。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

bugcoder-9905

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

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

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

打赏作者

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

抵扣说明:

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

余额充值