#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;
}
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;
}
}
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;
}
先构造临时对象—>使用临时对象进行拷贝构造目标对象—>析构临时对象
以上三个步骤会被编译器直接优化为:构造目标对象
临时对象有常属性,是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;
}
以上代码执行过程太复杂,需要优化。
建议:
- 函数传递参数,能传引用就传引用。如果确定函数中不会修改参数的值,就使用
const引用
。 - 如果要返回的数据是一个新产生的对象,可以返回临时量就返回临时量,并且使用返回的临时量直接构造新的对象。
优化为如下形式:
堆区申请对象
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都是浅拷贝。
赋值运算符: 如果不重写赋值运算符,默认也是一个浅拷贝。
常对象不能调用普通方法: 普通方法的形参为普通指针,而实参为常量,不能泄露地址给不同指针。