拷贝构造
class Stu{
public:
int no;
string name;
int age;
public:
Stu(int no=10086, string name="jin", int age = 18):no(no), name(name), age(age){}
//拷贝构造函数:由同类型的对象构造一个新的副本对象
Stu(const Stu &s){
no = s.no;
name = s.name;
agr = s.age
}
};
int main()
{
Stu s1 = (10010, "jink", 100);
//拷贝构造,但是调用的并不是Stu这个构造函数,而是拷贝构造函数
Stu s2(s1);
}
定义
由已经存在的同类型对象构造一个新的副本对象,调用的是拷贝构造函数、
本质
拷贝构造函数也是构造函数,是一个特殊
如果在实现一个类时 没有为这个类添加拷贝构造函数,则编译器会提供一个默认的
默认的拷贝构造函数是:全员复制(逐字节拷贝)副本和元对象一摸一样
程序员可以俺自己的需求构造拷贝构造函数,构造后编译器不再提供默认拷贝构造函数
使用时机
- 非引用传递对象
- 放入容器的对象都是通过拷贝构造函数
- 用存在的对象构造新的同类型的对象
调用语法
- 类名 对象1 = 对象2;
- 类名 新对象(老对象);
非默认拷贝构造函数的使用时机
- 如果需要实现深拷贝时则需要自己实现拷贝构造函数 默认的拷贝构造函数是浅拷贝
- 有指针指向动态内存时一般需要实现拷贝构造函数
- 浅拷贝——按照字节拷贝 按字节复制 拷贝对象和原对象一模一样
- 深拷贝——对于普通数据而言没有深拷贝一说,拷贝指针所指的内容
- 对于指针,重新申请内存,把原指针指向的内容拷贝的新的内存空间中
所以当有指针指向动态内存时,一般需要使用独立构造的深拷贝
class Ptr{
private:
int *ptr;
public:
Ptr(const Ptr &pp){}
}
拷贝赋值函数
拷贝赋值的时点
当两个已经存在的同类型对象之间的相互赋值 ,调用的就是拷贝赋值
拷贝赋值函数形如:
class 类名{
类名& operator(const 类名 &对象名){}
}
如果一个类没有实现拷贝赋值函数,编译器提供默认的拷贝赋值函数,且为浅拷贝
若需要实现深拷贝,则需要程序员手动提供
一般来说:遵循三/五原则
- C++11之前为三:析构,拷贝构造,拷贝赋值,要么全部自己提供 ,要么全用默认
- C++11之后为五:析构,拷贝构造, 拷贝赋值,移动构造, 移动赋值
实现简单的string类
class String{
public:
//用C风格的字符串构造 String对象
String(const char *s = NULL):
str(strcpy(new char[s?strlen(s)+1:1, s?s:"")){}
~String(void){
if(str != nullptr){
delete[] s;
str = nullptr;
}
}
//拷贝构造函数 用同类型的对象拷贝构造一个同类型的副本对象
String (const String &s):
str(strcpy(new char[strlen(s.str)+1, s.str){}
String &operator=(const String& ss){
if(this != s){
String _tmp(s);
swap(str,_tmp.str);
//看不到释放内存 但它有释放 delete [] _tmp.str
//_tmp对象生命周期在语句块结束之后 到期 自动调用析构
}
return *this;
}
size_t length(void)const{
return strlen(str);
}
const String *c_str(void)const{
return str;
}
prevate:
char *str;
};
封装一个栈以及简单测试:
#include <bits/stdc++.h>
using namespace std;
class Stack{
public:
Stack(int cap, int sizes = 0):
cap(cap){
elems = new int[cap];
}
//elems(new int[cap])
~Stack(void){
if(sizes != 0){
delete[] elems;
elems = nullptr;
}
}
Stack(Stack &stack){
cap = stack.capacity();
sizes = stack.size();
int *Elem = new int[stack.cap];
for(int i = 0; i < stack.size(); i++){
Elem[i] = stack.elems[i];
}
}
Stack &operator = (Stack &stack){
if(this != &stack){
Stack _tmp(stack);
swap(elems, _tmp.elems);
cap = stack.capacity();
sizes = stack.size();
}
return *this;
}
void push(int elem){
if(full()){
throw out_of_range("full");//抛出异常<stdexcept>
}
elems[sizes++] = elem;
}
int pop(void){
if(empty()){
throw out_of_range("empty");
}
return elems[--sizes];
}
bool full(){
return cap == sizes;
}
bool empty(){
return sizes == 0;
}
int top(void){
if(empty()){
throw out_of_range("empty");
}
return elems[sizes-1];
}
int size(void){
return sizes;
}
int capacity(void){
return cap;
}
private:
int cap;
int sizes;
int *elems;
};
int main()
{
Stack stack(10);
for(int i = 0; i < 10; i++){
stack.push(i);
}
for(int i = 0; i < 10; i++){
cout<<stack.pop()<<endl;
}
}
默认的无参构造
- 默认无参构造:在一个类没有实现构造函数到时候编译器自动提供的,
- 如果需要显式调用有参构造函数,需要提供成员名和实参
- 默认拷贝构造函数:会在初始化列表中调用类类型成员的拷贝构造函数
- 默认拷贝赋值函数:会在函数体中调用类类型的成员的拷贝赋值函数
- 默认析构函数:会自动调用类类型 的成员的析构函数
对一个空类至少有以下几个成员函数
- 无参构造
- 拷贝构造
- 拷贝赋值
- 析构函数
- & 取值函数 T* operator&(void)
- & 常取址函数 const T* operator&(void)const
- 如果在一个类中只提供拷贝构造函数,则其只有拷贝构造函数一个成员函数
移动构造
class 类名{
public:
类名(类名&& obj){
}
};
移动赋值
- 用一个存在的对象给另外一个存在的对象的进行赋值,正常情况下,被赋值的资源应该析构,然后把另外一个对象的资源拷贝一份给被赋值的对象,但是如果=右边的对象马上就会消亡时,为了效率着想,就没有必要拷贝一份给被赋值的对象,直接把资源交给(转移给)被赋值的对象
- =右边的对象不能再使用了
class 类名{
public:
类名& operator=(类名&& obj){
}
};
单例模式
模式:总结出的开发套路,能减少代码重复度,提高代码可靠性和效率,且实现特定的功能
单例模式:
定义:一个类只能创建一个对象
- 懒汉模式
- 不到万不得已不会动,只有饿了才会找吃的
- 单例模式的对象只有有需求时才会创建
class SingleTon{
public:
static SingleTon* getInstance(void){
if(ps == nullptr){
ps = new SingleTon();
}
return ps;
}
void realse(void){
--cnt;
if(cnt == 0){
delete ps;
ps = nullptr;
}
}
~SingleTon(){
}
private:
int no;
string name;
private:
SingleTon(){}
SingleTon(const SingleTon& s){}
static SingleTon *ps;//指向唯一实例的指针
static int cnt;
};
SingleTon SingleTon::ps = nullptr;
int SingleTon::cnt = 0;
int main()
{
SingleTon *p1 = SingleTon::getIntance();
}
优缺点
- 只有有需求时才会创建,节省内存空间,用完了可以及时释放
- 线程不安全 两个线程同时去创建可能会创建多份实例,所以需要考虑线程同步,线程同步效率变低
- 饿汉模式
- 很饿,需要时刻准备吃的
- 该模式下单例模式对象一直存在,无论是否有需求
class SingleTon{
public:
static SingleTon* getInstance(void){
return s;
}
~SingleTon(){}
private:
int no;
string name;
static SingleTon s;
private:
SingleTon(){}
SingleTon(const SingleTon& s){}
static SingleTon s;//唯一实例
};
SingleTon SingleTon::s;
优缺点
- 不管是否有需求,一直占用内存,浪费内存空间
- 饿汉模式是在程序加载阶段就去实例,能够保证只有一个线程,线程安全的