类和对象
0.C/C++的区别
C语言:面向过程的语言
C语言:计算机语言
C语言的缺陷就是把属性和行为分离设计
- 计算机语言设计的目的和意义是什么:
通过计算机来解决现实中的问题(模拟现实) - 模拟现实中的什么?
结构体–>实体属性,函数—>行为 - 现实中的东西叫实体—》用属性和行为来描述
C++:面向对象的语言
0.1.C语言结构体
C语言结构体
typedef struct Data
{
int ma;
int mb;
}DATA;
void SetValue(DATA* pdt,int a,int b) //赋值
{
pdt->ma=a;
pdt->mb=b;
}
int main()
{
DATA val;
setValue(&val,10,20);
return 0;
}
0.2把变量和函数联系在一起
把变量和函数联系在一起---->函数指针
typedef struct Data
{
int ma;
int mb;
void(*func)(struct Data*, int, int);
}DATA;
//void (*) (struct Data*,int,int);
void SetValue(DATA* pdt, int a, int b)
{
pdt->ma = a;
pdt->mb = b;
}
int main()
{
DATA val;
val.func = &SetValue;
val.func(&val, 10, 20); //可以理解为实体在执行行为
//val.func(10,20);
return 0;
}
1.类和对象
1.1 oop思想(面向对象语言的思想)
1.2写一个人类
//class 类标识
class People
{
public:
void eat(char* what)
{
std::cout << mname << " is eatting " << what << std::endl;
}
void sleep(char* _where)
{
std::cout << mname << " is sleepping at" << _where << std::endl;
}
void play()
{
std::cout << mname << " is playing doudou!" << std::endl;
}
private:
char mname[10];//姓名
bool msex;//性别
int mage;//年龄
};
/*
设置点起 到类结束或者下一个访问限定符为止
*/
/*
类默认的访问限定符
private:
*/
int main()
{
//int a;//类型 生成 变量
People p1;//People 类型 类类型 类型 实例化 对象
//访问限定符,导致访问失败
//strcpy_s(p1.mname, strlen("xiaoming"), "xiaoming");
p1.eat("肉");
return 0;
}
2.C++三大特征
C++三大特征 :
封装 继承 多态
访问限定符
从设置点起,到类结束或者下一个访问限定符为止,默认为private
1.public: 任意访问
2.protected: 子类和本类类中
3.private: 本类类中
2.1封装(该让你看的能看见,不该让你看的看不见)
封装就是: 属性和行为实现保护起来
流程应该是:要钱,好,然后小明从口袋拿钱递给我
属性是私有的,是需要保护的
行为是公有的,public
设计一个学生类
#include<iostream>
class Student
{
private:
char name[10];
bool sex;
int age;
public:
void learn(const char* what)//
{
std::cout << name << "learning" << what << std::endl;
}
void eat(const char* when, const char* what)
{
std::cout << name << "eating" << what << "on" << when << std::endl;
}
void sleep(const char* when, const char* what)
{
std::cout << name << "sleeping at" << what << "on" << when << std::endl;
}
};
2.2.成员变量和成员方法-----和对象的关系?
3.this
this指针指向对象的说法太片面,this指针指向 对象所占的空间(没有资源)
this指针不允许修改,指向不能发生变化(自身存储的地址不能变)
3.1thiscall调用约定
成员方法调用依赖对象调用
4.类中默认的函数
如果设计者没有提供,那么系统会提供六个默认函数
1.构造函数
2.析构函数
3. 拷贝构造函数
4. 赋值运算符的重载函数
5. 取地址操作符的重载函数
6. const修饰的取地址操作符的重载函数
4.1.C语言中的类的初始化和销毁
类的初始化函数:
类的销毁函数:
主函数调用
4.2C++中的构造函数和析构函数
4.2.1构造函数
构造函数的作用:
初始化对象所占的内存空间(赋予对象所占的内存空间资源)
构造函数(可以重载,“生而不同”)
有this指针(如果没有的话,没法确定给哪一个成员变量做初始化)
不可以人为调用,只能系统调用(构造函数是thiscall调用,不依赖对象调用,构造函数没有调用完成,对象是不会生成的)
构造函数没有调用完成:对象不完整----->半成品对象
生成对象的步骤
- 开辟对象所占的内存空间<-----系统提供的
- 调用构造函数(初始化对象所占的内存空间)<-----系统提供的
对象是什么?
空间+资源
变量是什么?
空间
定义和实例化的区别?
定义----->空间(开辟)
实例化---->空间+资源(开辟+赋予资源)
系统提供的构造函数应该长什么样子?
90行出现歧义,91行才是默认构造函数的调用
4.2.2析构函数
析构函数(不能重载,“死了都一样”)
析构函数的作用:
释放对象所占的其他资源
析构函数(不能重载,“死了都一样”)
有this指针
可以人为调用
实现:
delete[] mname------>销毁堆上开辟的内容
调用时间:
对象的销毁:
1.调用析构函数(释放对象所占的其他资源)
2.系统释放对象所占的空间----->栈上的内容
4.2.2构造函数和析构函数区别
1.构造函数的作用:
初始化对象所占的内存空间(赋予对象所占的内存空间资源)
构造函数(可以重载,“生而不同”)
有this指针(如果没有的话,没法确定给哪一个成员变量做初始化)
不可以人为调用,只能系统调用(构造函数是thiscall调用,依赖对象调用,构造函数没有调用完成,对象是不会生成的)
2.析构函数的作用:
释放对象所占的其他资源
析构函数(不能重载,“死了都一样”)
有this指针
可以人为调用,但是会退化成普通函数调用,程序结束后会再调用一次。
3.区别:
生来赋予的属性是不可以修改的,不能人为调用。
而消亡的过程可以人为操控。
4.构造函数和析构函数的顺序:
先构造后析构
开辟空间是在栈里,1先入栈然后23入栈,栈是先入后出,所以销毁时候,3先出栈。
4.2.3练习:C++封装链表(友元关系)
友元关系:
1.单向性
#include<iostream>
class CLink; //前置声明
class Node //结点类
{
public:
Node(int data = 0);//给一个默认值
private: //如果把私有改成公有,数据的保护无法完成
int mdata;
Node* next;
friend class CLink;
};
Node::Node(int data)//函数名前加类的作用域--》表示函数是类的一个成员方法
{
mdata = data;
next = NULL;
}
class CLink //链表类--》结点的集合
{
public:
CLink();
~CLink();
//头插 尾插 头删 尾删 打印
void InsertOfHead(int val);
void InsertOfTail(int val);
bool Empty();
bool DeleteOfHead();//要判断是否为空
bool DeleteOfTail();
void Print();
private:
Node* phead;
};
CLink::CLink()
{
phead = new Node(); //头指针指向头结点
}
CLink::~CLink()
{
//释放头结点和数据节点
Node* p = phead;
Node* tmp;
while (p != NULL)
{
tmp = p->next;
delete p;
p = tmp;
}
phead = NULL;
}
void CLink::InsertOfHead(int val)
{
Node* s = new Node(val);
s->next = phead->next;
phead->next = s;
}
void CLink::InsertOfTail(int val)
{
Node* s = new Node(val);
Node* tail = phead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = s;
}
bool CLink::Empty()
{
//头结点的指针域是否为空
return phead->next == NULL;
}
bool CLink::DeleteOfHead()
{
if (Empty())
{
return false;
}
Node* q = phead->next;
phead->next = q->next;
delete q;
}
bool CLink::DeleteOfTail()
{
if (Empty())
{
return false;
}
//删除最后一个节点,最后一个的前一个的指针应该指向为空
Node* tail0 = phead;//倒数第二个节点
Node* tail = tail0->next;//最后一个节点
while (tail->next != NULL)
{
tail0 = tail0->next;
tail = tail0->next;
}
tail0->next = NULL;
delete tail;
return true;
}
void CLink::Print()
{
Node* p = phead->next;
while (p!= NULL)
{
std::cout << p->mdata << " ";
p = p->next;
}
std::cout << std::endl;
}
int main()
{
CLink cl;
for (int i = 0; i < 5; ++i)
{
cl.InsertOfHead(i + 1);
}
cl.Print();
for (int i = 0; i < 5; ++i)
{
cl.InsertOfTail(i + 1);
}
cl.Print();
cl.DeleteOfHead();
cl.Print();
cl.DeleteOfTail();
cl.Print();
return 0;
}
4.3拷贝构造函数
用一个已经存在的对象来生成相同类型的新对象
必须用引用当形参,防止递归调用拷贝构造函数
class CGoods
{
public:
CGoods()
{
}
CGoods(int amount)
{
mamount = amount;
}
CGoods(char* name, float price, int amount)
{
mname = new char[strlen(name) + 1]();
strcpy_s(mname, strlen(name) + 1, name);
mprice = price;
mamount = amount;
}
//深拷贝
CGoods(const CGoods& rhs)
{
mname = new char[strlen(rhs.mname) + 1]();
strcpy_s(mname, strlen(rhs.mname) + 1, rhs.mname);
mprice = rhs.mprice;
mamount = rhs.mamount;
}
/*
1. 防止形参修改实参的值
2. 接收隐式生成的临时量----》必须用常引用来接收临时量(常量)
*/
//void CGoods&
void operator=(const CGoods& rhs)//this:左操作数 形参:右操作数
{
if (this == &rhs)//自赋值
{
return;
}
delete[] mname;
mname = new char[strlen(rhs.mname) + 1]();
strcpy_s(mname, strlen(rhs.mname) + 1, rhs.mname);
mprice = rhs.mprice;
mamount = rhs.mamount;
}
~CGoods()
{
delete[] mname;
mname = NULL;
}
private:
char* mname;
float mprice;
int mamount;
};
int main()
{
CGoods good1("面包", 4.5, 100);
CGoods good2 = good1;----->拷贝构造函数
CGoods good2("牛奶", 2.5, 1000);
good2 = good1;//编译器通过-》赋值运算符的重载函数,进行赋值----->operator,详细解释见4.4
return 0;
}
4.3.1拷贝构造函数
拷贝构造函数
4.3.2浅拷贝构造函数
默认拷贝构造函数:浅拷贝(如果成员变量有指针,考虑是否实现深拷贝)
指向了同一个内存区域,good2销毁时,释放了mname内存,当good1销毁时候,释放内存就会出现重复释放错误。
4.3.3为什么拷贝构造函数的形参一定是引用&
拷贝构造函数如果形参不加引用,递归调用生成形参对象,导致栈溢出,程序崩溃
4.4. 赋值运算符的重载函数(浅拷贝,要实现自己写的深拷贝版本)
用一个已存在的对象赋给相同类型的已存在对象
构造的步骤:
1.判断是否是自赋值
2.释放旧资源
3.开辟新资源
4.赋值
4.4.1面试常问的几个问题
4.4.1.1.class和struct的区别
默认访问限定符不同:
class:
private
struct:
public
4.4.1.2.空结构体的大小多大,为什么? 空类呢?
C:(用模子来刻出内存块,空结构体相当于不存在的模子,是无法刻画内存块的)
不能定义空结构体
C++:
1 个字节
结构体是当类类型来处理的
空类:
1
类模拟的是抽象概念,是从实体中抽象出来的,实体是现实中存在的,因此,C++设计者将空类的大小默认设置为1,有空间的对象就能模拟出实体
struct Data
{
public:
void Show()
{
std::cout << "hello world" << std::endl;
}
};
//只有成员方法没有成员变量,也被认为是空类
class Test
{
public:
void Show()//_thiscall
{
std::cout << "hello world!" << std::endl;
}
};
int main()
{
Test test;//如果是0 则test不存在的对象
test.Show();
//Data val;
//val.Show();
//std::cout << sizeof(struct Data) << std::endl;
return 0;
}
4.4.1.3.赋值运算符的返回值类型
class CGoods
{
public:
CGoods()
{
mname = new char[1]();
}
CGoods(char* name, float price, int amount)
:mname(new char[strlen(name) + 1]()),
mprice(price),
mamount(amount)
{
strcpy_s(mname, strlen(name) + 1, name);
}
CGoods(const CGoods& rhs)
{
mname = new char[strlen(rhs.mname) + 1]();
strcpy_s(mname, strlen(rhs.mname) + 1, rhs.mname);
mprice = rhs.mprice;
mamount = rhs.mamount;
}
CGoods& operator=(const CGoods& rhs)
{
if (this != &rhs)
{
delete[] mname;
mname = new char[strlen(rhs.mname) + 1]();
strcpy_s(mname, strlen(rhs.mname) + 1, rhs.mname);
mprice = rhs.mprice;
mamount = rhs.mamount;
}
return *this;
}
~CGoods()
{
delete[] mname;
mname = NULL;
}
private:
char* mname;
float mprice;
int mamount;
};
int main()
{
int a = 10;
int b = 20;
int c;
c = a = b;//连续赋值,自右向结合性
CGoods good1("面包", 4.5, 100);
CGoods good2("牛奶小面包", 4.5, 100);
CGoods good3;
good3 = good2 = good1;
//good2 = good1; //返回值应该是good2本身 ,才能正确的赋给good3
return 0;
}
4.5. 取地址操作符的重载函数
4.6. const修饰的取地址操作符的重载函数
5.临时对象
临时对象:
生存周期:表达式结束,遇到分号,生存周期就结束了
5.1临时对象的优化
临时对象的优化:
5.2临时对象的分类
- 显式生成临时对象:
明确指出生成怎么样的临时对象 - 隐式生成临时对象:
编译器推演出应该生成怎样的临时对象
80行是隐式生成,82行是显式生成(只有类型,没有对象名称,就是显式生成)
5.3临时量的属性
- 内置类型-----》常量
- 自定义类型----》变量
- 隐式生成的临时对象----》常量
5.4引用能提升临时对象的生存周期
引用会把 临时对象的生存周期 提升的和 引用变量的生存周期 相同
95行的代码:把临时对象的地址赋给指针的指向,当语句结束后临时对象销毁,指针指向的就是无效对象
6.对象的生存周期
CGoods ggood1("g1", 4, 10);//.data 程序加载到程序结束后销毁
static CGoods ggood2("g2", 2, 10);//.data---》整个程序结束后销毁 程序加载到程序结束
int main()
{
CGoods lgood1;//stack
CGoods lgood2("l2", 10, 20);//stack
static CGoods lgood3("l3", 30, 10);//.data---》整个程序结束后销毁
CGoods lgood4 = CGoods("l4", 40, 10);//stack
CGoods lgood5 = 20;//stack (有优化产生,只生成了lgood5对象)
lgood1 = CGoods("tmp", 20, 30);----》不是生成新对象,所有没有优化,表达式完成临时量销毁,有构造,赋值,析构调用
lgood2 = 30;
lgood3 = (CGoods)(10,20,30,40);//强转 逗号表达式 {}
/*
逗号表达式
数据最终只选择最后一个
*/
CGoods* pc1 = new CGoods("pc1", 10, 20);//堆
CGoods* pc2 = new CGoods[2];//堆 有两个对象生成
CGoods* pc3 = &CGoods("pc3", 10, 20);
CGoods& rc3 = CGoods("rc3", 10, 20);
delete pc1;
delete[] pc2;
return 0;
}
CGoods ggood3("g3", 3, 10);//.data
7.类类型返回值:都是通过临时对象带出来
非类类型返回值方式:
1.<=4 eax
2. >4 && <=8 eax edx
3.>8 临时量
问有几个对象生成?
答:五个,test1,test2,arg,tmp,return时候的返回值对象。
class Test
{
public:
Test(int a = 0)
{
ma = a;
}
Test(const Test& rhs)
{
ma = rhs.ma;
}
~Test()
{
std::cout << "Test::~Test()" << std::endl;
}
private:
int ma;
};
Test getObject(Test arg)
{
Test tmp = arg;
//...
return tmp; //临时量
}
int main()
{
Test test1(20);
Test test2;
test2 = getObject(test1);
return 0;
}
class Test
{
public:
Test(int a = 0)
{
std::cout << "Test::Test(int)" << std::endl;
ma = a;
}
Test(const Test& rhs)
{
std::cout << "Test::Test(const Test&)" << std::endl;
ma = rhs.ma;
}
void operator=(const Test& rhs)
{
std::cout << "Test::operator=(const Test&)" << std::endl;
if (this == &rhs)
{
return;
}
ma = rhs.ma;
}
~Test()
{
std::cout << "Test::~Test()" << std::endl;
}
private:
int ma;
};
/*
自定义类型做形参
一般都会设为引用(引用不生成对象,少了一个构造和析构)
效率提高
*/
Test getObject(Test& rhs)--->引用不生成
{
Test tmp = rhs;//2
//...
return tmp;//返回了一个对象
}
int main()
{
Test test1(10);//1
Test rt = getObject(test1);//3---》返回值传过来,产生优化,只生成rt
return 0;
}
7.1构造函数的初始化列表
8.const和static修饰类成员的用法
8.1 const修饰
- 修饰成员变量:一定要初始化
- 修饰成员方法:常方法
常对象不能调用普通方法,常对象只能调用常方法(普通方法有修改常变量值的风险)
普通对象能调用常方法
常方法中不能调用普通成员方法
普通成员方法中能调用常方法
class Test
{
public:
Test(int a) : ma(a)
{}
void Show()const //const Test* const this;
{
std::cout << "ma:" << ma << std::endl;
}
void Print()
{
Show();
std::cout << "hello world!" << std::endl;
}
private:
int ma;
};
int main()
{
Test test(10);
test.Show();
return 0;
}
8.2 面试问题:简述一下const
1.变量:C/C++
2.不允许修改请求处理
3.const修饰成员变量
8.3 static修饰成员
1.修饰成员变量:
初始化方式:成员变量不属于对象 ,属于类 (属于所有对象共享),一定要在类外初始化
访问:不依赖对象访问
2.修饰成员方法:
_cdecl 没有this指针 不能访问普通的成员变量
只能访问:全局变量 静态的成员变量
访问:不依赖对象访问
静态的成员方法不能调用普通的成员方法
普通的成员方法能调用静态的成员方法
构造函数初始化对象所占的内存空间
静态成员变量不属于对象,因此构造函数不能初始化静态成员变量
class Test
{
public:
Test(int b)
: mb(b)
{}
static void Show()//cdecl 没有this指针
{
//Print();
std::cout << "ma:" << ma << std::endl;
//std::cout << "mb:" << mb << std::endl;
}
void Print()//thiscall
{
std::cout << "mb:" << mb << std::endl;
Show();
}
static int ma;
private:
int mb;
};
int Test::ma = 10;
int main()
{
Test test1(10);
Test test2(20);
std::cout << test1.ma << std::endl;
std::cout << Test::ma << std::endl;//
test1.Show();
Test::Show();
test1.Print();
return 0;
}
8.4函数指针在调用点调用函数方法
//int (*) (int,int)
int Sum(int a, int b)
{
std::cout << "::Sum(int,int)" << std::endl;
return a + b;
}
class Test
{
public:
Test(int a)
:ma(a)
{}
void Show()
{
std::cout << "ma:" << ma << std::endl;
}
private:
int ma;
};
/*
void (*) (); //Test::
*/
/*
.* ->*
函数指针指向类成员方法后,调用时
*/
int main()
{
int(*Func) (int, int);
Func = ∑
(*Func)(10, 20);
//Sum(10, 20);
Test test(10); ------》依赖对象调用
void(Test::* cppfunc)() = &Test::Show;
(test.*cppfunc)();
Test* ptest = new Test(20);
(ptest->*cppfunc)();
return 0;
}
8.5 练习
class String
{
public:
//NULL
String(char* ptr)
{
assert(ptr!=NULL);
if(ptr==NULL)
{
return;
}
mptr = new char[strlen(ptr) + 1]();
strcpy_s(mptr, strlen(ptr) + 1, ptr);
}
String(const String& rhs)
{
mptr = new char[strlen(rhs.mptr) + 1]();
strcpy_s(mptr, strlen(rhs.mptr) + 1, rhs.mptr);
}
String& operator=(const String& rhs)
{
if (this == &rhs)
{
return *this; //---->左操作数
}
delete[] mptr;
mptr = new char[strlen(rhs.mptr) + 1]();
strcpy_s(mptr, strlen(rhs.mptr) + 1, rhs.mptr);
return *this;
}
~String()
{
delete[] mptr;
mptr = NULL;
}
private:
char* mptr;//
};
int main()
{
String str("hello");
String str2 = str;
str2 = str;
return 0;
}
9.单例模式
设计模式
单例模式
类只能生成一个对象
1.屏蔽生成对象的接口
构造函数放在private
2.提供接口来生成唯一对象
1.不能类类型的方式返回
2.static
9.1 设计一个校长类(单例模式:懒汉模式)
/*
一个接口目的是为了生成对象
一般都会写出static
*/
class Rector
{
public:
static Rector* getInstance(char* name, int age, bool sex)//----》获取实例
{
if (pre == NULL)
{
pre = new Rector(name, age, sex);
}
return pre;
}
private:
Rector(char* name, int age, bool sex)
{
mname = new char[strlen(name) + 1]();
strcpy_s(mname, strlen(name) + 1, name);
mage = age;
msex = sex;
}
char* mname;
int mage;
bool msex;
static Rector* pre;//标识唯一对象 不属于对象 属于整个类的作用域
};
Rector* Rector::pre = NULL;
int main()
{
Rector * pr1 = Rector::getInstance("zhangsan", 45, true);
Rector * pr2 = Rector::getInstance("zhangsan", 45, true);
Rector * pr3 = Rector::getInstance("zhangsan", 45, true);
//Rector re1("zhangsan", 45, true);
//Rector re2("lisi", 45, true);
return 0;
}
9.1.1单例模式生成类的 函数值返回类是什么
&、*都可以,因为不生成临时对象(生成临时对象就会产生两个对象,单例模式是唯一的)
构造函数和拷贝构造函数都可以生成对象,因此不仅要考虑构造,拷贝构造也需要考虑,因此9.1的代码不够完善
9.2把校长类抽象成模型(线程安全)
9.2.1 未加锁
1.不加锁的情况:会产生不安全因素
9.2.2加锁
2.加锁后,第一次 安全有了保障,但是第二次第三次仍然要加锁解锁,产生了资源消耗
9.2.3 双重锁
3.面对资源消耗:双重锁单例机制
9.2.4饿汉模式:贪婪加载
线程是进程的一条执行路径
线程中处理 才生成唯一对象 ----->不安全
线程开启前 生成唯一对象-----》安全
class SingleTon
{
public:
static SingleTon* getInstance() //生成一个接口
{
return psing;
}
private:
SingleTon()
{}
SingleTon(const SingleTon&);
static SingleTon* psing;//.data
};
SingleTon* SingleTon::psing = new SingleTon();//main执行之前
int main()
{
SingleTon* psingle1 = SingleTon::getInstance();
SingleTon* psingle2 = SingleTon::getInstance();
SingleTon* psingle3 = SingleTon::getInstance();
return 0;
}
9.2.5懒汉模式:延迟加载
class SingleTon
{
public:
static SingleTon* getInstance()
{
if (psing == NULL)//第二次及以后 线程安全
{
lock();
if (psing == NULL)
{
psing = new SingleTon();
}
unlock();
}
return psing;
}
private:
SingleTon()
{
}
SingleTon(const SingleTon&);
static SingleTon* psing;
};
SingleTon* SingleTon::psing = NULL;
int main()
{
SingleTon* psingle1 = SingleTon::getInstance();
SingleTon* psingle2 = SingleTon::getInstance();
SingleTon* psingle3 = SingleTon::getInstance();
return 0;
}