前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >类和对象(构造深入)

类和对象(构造深入)

作者头像
小飞侠xp
发布2018-12-27 17:03:31
9450
发布2018-12-27 17:03:31
举报
数据成员指针

定义: 数据类型类名:: *指针名 = &类名::数据成员

解引用: 对象名.* 指针名 对象指针 ->*指针名

数据成员指针实际上是一个偏移量,区别于普通指针。

static静态成员变量不能使用数据成员指针

代码语言:javascript
复制
#include <iostream>
using namespace std;

struct A {
    A(double n1=0.0,int n2=0):num1(n1),num2(n2){}
    double num1;
    int num2;
};

int main(){
    int i = 10;
    int *pi = &i; //指针 指向普通变量

    A a(0.1, 1), b(1.1, 2);
    int *pa = &a.num2;
    cout << a.num2 << endl; //1

    int A::*p = &A::num2; //指针 指向成员变量
    cout << a.*p << b.*p << endl; // 1 2

    //指向数据成员的指针,是针对类的,是一个偏移量
    printf("%p\n", p); // 00000008 

    A* pA = new A(3.1, 3);
    cout << pA->*p << endl; //3

    auto p_double = &A::num1;
    cout << typeid(p_double).name() << endl; //double A::*

    delete pA;
    return 0;
}
成员函数指针

普通函数指针:返回值类型(*指针名)(参数列表) 注意:void( * p_fun)(int,int);和void * p_fun(int,int);的区别 前者是定义函数指针,后者是函数声明(指针函数)

成员函数指针的定义: 返回值类型(类名:: * 指针名)(参数列表) 解引用: (对象名.* 指针名) (参数列表) (对象指针 ->*指针名)(参数列表) 非静态成员函数指针与普通成员函数的区别是,隐含参数this指针 静态成员函数由于没有this指针,所以和普通函数指针是一样的,不能类名:: * 指针名

成员函数指针赋值时,不能省略&(取地址符)

代码语言:javascript
复制
#include <iostream>
using namespace std;

struct A {
    int add(int a, int b) { cout << "add()" << endl; return a + b; }
    static void show() { cout << "show()" << endl; }
};
void f1(int a, int b) { cout << "f1()" << endl; }
void f2(int a, int b) { cout << "f2()" << endl; }
typedef void(*P_FUN)(int, int);  //typedef 定义函数指针
using P_FUN1 = void(*)(int, int);//using 定义函数指针

typedef int(A::*P_ADD)(int, int);
using P_ADD1 = int(A::*)(int, int);
int main(){
    void(*p_fun)(int, int) = &f1; //普通函数指针,可省略&
    p_fun(2, 2); // f1()
    p_fun = &f2;
    p_fun(1, 2); // f2()

    P_FUN p1 = &f1;
    p1(1, 1);  // f1()
    p1 = &f2;
    p1(2, 2);  // f2()

    P_FUN1 p2 = &f1;
    p2(1, 1);  // f1()
    p2 = &f2;
    p2(2, 2);  // f2()

    //非静态成员函数指针,区别于普通函数,主要是由于隐含的this指针
    int(A::*p_add)(int, int) = &A::add; //非静态成员函数指针
    A a;
    cout << (a.*p_add)(0,2) << endl;     //.* 必须有对象,注意括号 运行结果add() 2
    A* pa = new A;
    cout << (pa->*p_add)(1, 2) << endl;  //->* //运行结果add() 3

    auto p_add1 = &A::add; 
    cout << (a.*p_add1)(2, 2) << endl;  //运行结果add() 4

    P_ADD p11 = &A::add;
    cout << (a.*p11)(3, 2) << endl; //运行结果add() 5
    P_ADD1 p22 = &A::add;
    cout << (pa->*p22)(2, 4) << endl; //运行结果add() 6

    delete pa;

    void(*p_show)() = &A::show; //静态成员函数指针 与普通函数指针类似
    p_show();
    return 0;
}
应用举例 :GAME类的方向移动函数封装

using Action = void(Game::*)();//定义别名 成员函数指针 相当于typedef void(Game::*Action)();

enum Direction { LEFT, RIGHT, UP, DOWN }; //枚举 默认从0开始,相当于LEFT=0,RIGHT=1,UP=2,DOWN=3 static Action menu[]; 数组中存放的是 成员函数指针

三五法则

默认构造函数,拷贝构造函数,赋值运算符重载,析构函数,系统可自动合成。(自己没有定义的时候) 拷贝构造函数,赋值运算符重载,析构函数 一般情况下,要么都自己定义,要么都是系统合成。 有资源时,都自定义,没资源时,不必自己定义。 三个当中,只要有一个需要自定义,意味着其他两个也要自定义! 使用 =default; 显式要求编译器生成合成默认版本 使用 =delete; 定义为删除的函数。通知编译器不需要该函数。 private 也可以阻止拷贝,阻止赋值。 构造或析构函数定义为 private将无法在类外创建对象。 但是:构造public,析构private是可以用new创建对象的。

代码语言:javascript
复制
#include <iostream>
using namespace std;

class A {
public:
    A() = default;
    ~A() = default;
    A(const A&) = default;
    A& operator=(const A&) = default;
};
class B {
public:
    B() = default;
    B(const B&) = delete;
    B& operator=(const B&) = delete;
};
class C {
public:
    void destroy() { delete this; }
private:
    ~C() {};
};
int main() {
    A a1;
    A a2 = a1;
    B b1;
    //B b2 = b1; //错误,拷贝构造delete
    //C c1; //错误,析构函数是 private
    C* pc = new C;
    pc->destroy();
    return 0;
}

引用计数

思考:深拷贝一定就好吗?浪费内存空间(数据冗余存储)

深拷贝和赋值运算符重载(深)
代码语言:javascript
复制
#include <iostream>
#include <cstring>
using namespace std;
class myString {
public:
    myString(const char * pstr = NULL) {
        if (!pstr) {
            ps = new char[1];
            ps[0] = '\0';
        }
        else {
            ps = new char[strlen(pstr) + 1];
            strcpy(ps, pstr);
        }
    }
    myString(const myString &other):ps(new char[strlen(other.ps) + 1]) {
        strcpy(ps, other.ps);
    }
    myString &operator=(const myString &other) {
        if (this != &other) {
            delete[] ps;
            ps = new char[strlen(other.ps) + 1];
            strcpy(ps, other.ps);
        }
        return *this;
    }
    ~myString() {
        delete[] ps;
    }
private:
    char *ps;
};
int main() {
    myString s1 = "abc";
    myString s2 = s1;
    myString s3;
    s3 = s1;
    return 0;
}
浅拷贝
代码语言:javascript
复制
myString(const myString &other){
    ps = other.ps;//浅拷贝构造
} 
myString & operator = (const myString &other){
    ps = other.ps;
    return *this;//赋值运算符重载(浅)
}

思考:为了节约内存空间,使用浅拷贝,如何解决“重析构”“内存泄漏”的问题? 引用计数:增加一个计数器,记录当前指向同一块内存的次数,拷贝构造和赋值的时候: 计数+1,析构的时候: 计数-1 ,假如计数器==0,那么释放内存。 构造时:count = 1; 拷贝时:count++; 赋值时:count++; 析构时:

代码语言:javascript
复制
count--;
if(count == 0)
    delete[] ps;
代码语言:javascript
复制
class myString{
    char *ps;
    int count;//这样做不行
}
代码语言:javascript
复制
class myString{
    char *ps;
    static int count;
}//这样做也不行

正确做法:使用成员指针,计数器既有共通性,又有独立性。

代码语言:javascript
复制
class myString{
    char +ps;
    int *count;
}

构造时,为count开辟空间,并赋值 = 1, 在count == 0时,和ps同时释放内存。

构造函数

代码语言:javascript
复制
#include <iostream>
#include <cstring>
using namespace std;
class myString {
public:
    myString(const char * pstr = NULL) {
        if (!pstr) 
            ps = new char[1]{'\0'};
        else {
            ps = new char[strlen(pstr) + 1];
            strcpy(ps, pstr);
        }
        count = new int(1); //构造时开辟空间并置为1
    }
    myString(const myString &other) 
        :ps(other.ps), count(other.count) {
        (*count)++; //拷贝构造时 count + 1
    }
    myString &operator=(const myString &other) {//赋值运算符重载
        if (this != &other) {
            (*count)--; //本对象原来的计数器 -1
            if (*count == 0) {
                cout << "delete in = ." << endl;
                delete[] ps;
                delete count;
            }
            ps = other.ps;
            count = other.count;
            (*count)++;
        }
        return *this;
    }
    ~myString() { 
        (*count)--; //析构时 count - 1
        if (*count == 0) { //count为 0 释放内存
            delete[] ps;
            delete count;
            cout << "delete!" << endl;
        }
    }
    int get_count()const { return *count; }
private:
    char *ps;
    int *count;
};

int main() {
    myString s1("abc");
    myString s2 = s1; //拷贝构造
    cout << s1.get_count() << endl; //2
    myString s3("123");
    myString s4 = s3;
    cout << s3.get_count() << endl; //2
    s3 = s2;
    cout << s3.get_count() << endl; //3
    cout << s4.get_count() << endl; //1
    cout << "============" << endl;
    return 0;
}

写时拷贝

写的时候才会去更改,读的时候不会更改

代码语言:javascript
复制
void reverse() {
        if (*count > 1) { //除了自己外,还有人在用
            //重新复制一份以后,再处理
            (*count)--; //先把自己的计数去掉
            count = new int(1); //重新开辟新的计数空间
            char* tmp = ps; //保留原来的ps指针指向的位置
            ps = new char[strlen(tmp) + 1];
            strcpy(ps, tmp);
            //此时,本对象已经复制了一份拷贝,并新开了计数器
        }
        int len = strlen(ps) - 1;
        for (int i = 0; i < len / 2; i++) {
            char c = ps[i];
            ps[i] = ps[len - i];
            ps[len - i] = c;
        }
    }
SWAP 函数

函数重载,定义自己的SWAP的函数,交换指针

代码语言:javascript
复制
#include <iostream>
#include <cstring>
using namespace std;
class myString {
public:
    friend void swap(myString &a, myString &b);
    myString(const char * pstr = NULL) {
        if (!pstr) {
            ps = new char[1];
            ps[0] = '\0';
        }
        else {
            ps = new char[strlen(pstr) + 1];
            strcpy(ps, pstr);
        }
        cout << "Constructor: const char*" << endl;
    }
    myString(const myString &other) {
        ps = new char[strlen(other.ps) + 1];
        strcpy(ps, other.ps);
        cout << "Constructor: copy" << endl;
    }
    myString &operator=(const myString &other) {
        if (this != &other) {
            delete[] ps;
            ps = new char[strlen(other.ps) + 1];
            strcpy(ps, other.ps);
        }
        cout << "operator = " << endl;
        return *this;
    }
    ~myString() {
        delete[] ps;
        cout << "Delete[]" << endl;
    }
private:
    char *ps;
};

void std_swap(myString &a, myString &b) {
    myString tmp = a;
    a = b;
    b = tmp;
}
void swap(myString &a, myString &b) {
    std::swap(a.ps, b.ps);
}
int main() {
    myString s1 = "abc";
    myString s2 = "124";
    cout << "=============" << endl;
    std::swap(s1, s2); //std::swap函数
    cout << "=============" << endl;
    std_swap(s1, s2);  //模拟std::swap
    cout << "=============" << endl;
    //std::swap执行了深拷贝(拷贝构造和赋值运算符重载)
    swap(s1, s2); //自己定义的swap函数,没有深拷贝
    cout << "=============" << endl;
    return 0;
}
对象移动

拷贝构造生成临时量大量消耗资源,C++11对其进行了优化 移动语义是C++11的特性之一,利用移动语义可以实现对象的移动而非拷贝,在某些情况下,可以大幅度提升性能

移动概念:既然tmp马上要销毁,那么tmp指向的对内存资源是不是可以给别人?避免浪费

为了支持移动操作,引入“右值引用”。 右值引用:只能绑定到一个将要销毁的对象。因此:我们可以自由地将一个右值引用的资源“移动”到另外一个对象中。

左值持久,右值短暂。 由于右值引用只能绑定到临时对象: 1、所引用的对象将要被销毁; 2、该对象没有其他用户。 上面的2个特性意味着:使用右值引用的代码可以自由地接管所引用的对象的资源。 左值、右值,左值引用、右值引用 左右值鉴别最简单的办法:左值可以用取地址操作符”&“获取地址,右值无法使用”&“。

代码语言:javascript
复制
int i  = 10;
int &r = i;// ok,标准的左值引用
int &&rr = i;//错误,不能讲一个右值引用绑定到一个左值上
// int &r2 = i *10;//错误,i * 10是个临时量,内置类型有const属性,所以必须是const int &r2 = i * 10;
const int &r2 = i * 10;
int &&r3 = i * 10;//ok,右值引用
代码语言:javascript
复制
int x = 0;//对象实例,有名,x是左值
int* p = &++X;//可以取地址,++x是左值
++x = 10;//前置++返回的是左值,可以赋值
//p = &x++;//后置++操作返回一个临时对象,不能取地址或赋值,是右值,编译错误

函数返回非引用类型时,是个临时量,所以是右值 注意:变量是左值,右值引用以后,相当于延长了临时量的生命周期,此时的临时量已经转换为左值了。 int && rr3 = i *10; int &&rr4 = rr3; //错误, rr3已经是左值了!! 所以:引用(包括左值引用,右值引用)习惯上用在参数传递 和 函数返回值。

移动构造和移动赋值运算符重载
代码语言:javascript
复制
#include <iostream>
#include <cstring>
#include <string>
#include <utility>
using namespace std;

class myString {
public:
    myString(const char *str = nullptr); //构造
    myString(const myString &other);     //拷贝构造
    myString(myString &&other);          //移动构造
    myString &operator=(const myString &other);//赋值运算符重载
    myString &operator=(myString &&other); //移动赋值运算符重载
    ~myString() {
        if(ps)
            cout << ps << " --Destructor" << endl; 
        else
            cout << "ps is NULL" << " --Destructor" << endl;
        delete[] ps;
    }
    const char * c_str()const { return ps; }
private:
    char *ps;
};

myString::myString(const char *str) {
    if (str == nullptr) {
        ps = new char[1]{ 0 };
        cout << ps << " --Default constructor" << endl;
    }
    else {
        int length = strlen(str);
        ps = new char[length + 1];
        strcpy(ps, str);
        cout << ps << " --Str constructor" << endl;
    }
}

myString::myString(const myString &other) {//拷贝构造
    int length = strlen(other.ps);
    ps = new char[length + 1];
    strcpy(ps, other.ps);
    cout << ps << " --Copy constructor" << endl;
}
myString::myString(myString &&other):ps(other.ps) { //移动构造
    other.ps = nullptr;
    cout << ps << " --Move constructor" << endl;
}
myString &myString::operator=(const myString &other) { //赋值运算符重载
    if (this != &other) {
        delete[] ps;
        int length = strlen(other.ps);
        ps = new char[length + 1];
        strcpy(ps, other.ps);
    }
    cout << ps << " --Copy assignment" << endl;
    return *this;
}

myString &myString::operator=(myString &&other) {//移动赋值运算符重载
    if (this != &other) {
        delete[] ps;
        ps = other.ps;
        other.ps = nullptr;
    }
    cout << ps << "--Move assignment" << endl;
    return *this;
}

myString operator+(const myString &a, const myString &b) {
    int length = strlen(a.c_str()) + strlen(b.c_str());
    char * p = new char[length + 1];
    strcpy(p, a.c_str());
    strcat(p, b.c_str());
    myString tmp(p);
    delete[] p;
    return tmp;
}

myString fun() {
    myString tmp("allok");
    return tmp;
}
int main() {
    myString s1("abc");
    myString s2("123");
    myString s3 = s1; //拷贝构造
    cout << "1====================" << endl;
    myString s4(myString("ok"));
    cout << "2====================" << endl;
    myString s5 = s1 + s2; 
    // s1 + s2 是临时量(右值),调用移动构造给s4,马上析构
    cout << "3====================" << endl;
    myString s6 = fun();
    // fun()返回值 是临时量(右值),调用移动构造给s6,马上析构
    cout << "4====================" << endl;
    myString s7 = std::move(s6); //std::move函数,#include <utility>
    cout << (void*)s6.c_str() << endl; //s6中的资源ps=NULL了。
    cout << "5====================" << endl;
    return 0;
}

std::move 显式调用,强制移动。int &&r = std::move(r1);//将左值转换为右值 调用move以后,对r1只能赋值或者销毁,r1中的内容不再有意义。

vector动态增长

vector类似动态数组,所以在内存中是一段连续的内存。 观察sizeof(vector<myString>)sizeof(vector<int>)sizeof(vector<string>) 的大小,猜测vector的内存结构。

代码语言:javascript
复制
vector<myString> vs;
vs.size(); //此函数返回vector中的元素个数(已用空间数)
vs.capacity(); //此函数返回vector中的总空间个数
vs.reserve(n); //此函数预先分配一块指定大小为n个的内存空间。

添加元素时,如果vector空间大小不足,则会以原大小1.5倍重新分配一块较大新空间

代码语言:javascript
复制
// ConsoleApplication8.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
#pragma warning(disable:4996)
#define use_CRT_SECURE_NO_WARNINGS
#include <vector>
using namespace std;
int main() {
    vector<int> vs;
    cout << "size=" << vs.size() << " capacity=" << vs.capacity() << endl;
    for (int i = 0; i < 10; i++) {
        vs.push_back(i);
        cout << "size=" << vs.size() << " capacity=" << vs.capacity() << endl;
    }
    cout << "====================" << endl;
    vector<int> vs1;
    vs1.reserve(4);
    cout << "size=" << vs1.size() << " capacity=" << vs1.capacity() << endl;
    for (int i = 0; i < 4; i++) {
        vs1.push_back(i);
        cout << "size=" << vs1.size() << " capacity=" << vs1.capacity() << endl;
        cout << &vs1[0] << endl; //观察连续内存的起始地址
    }
    vs1.push_back(4);
    cout << "size=" << vs1.size() << " capacity=" << vs1.capacity() << endl;
    cout << &vs1[0] << endl; //观察连续内存的起始地址
    system("pause");
    return 0;
}

重新分配一块较大的新空间后,将原空间内容拷贝过来,在新空间的内容末尾添加元素,并释放原空间。也就是说vector的空间动态增加大小,并不是原空间之后的相邻地址增加新空间,因为vector的空间是线性连续分配的,不能保证原空间之后有供配置的空间

vector与移动
代码语言:javascript
复制
#include <iostream>
#include <cstring>
#include <string>
#include <utility>
#include <vector>
using namespace std;

class myString {
public:
    myString(const char *str = nullptr); //构造
    myString(const myString &other);     //拷贝构造
    myString(myString &&other) noexcept; //移动构造
    myString &operator=(const myString &other);//赋值运算符重载
    myString &operator=(myString &&other) noexcept; //移动赋值运算符重载
    ~myString() {
        if (ps)
            cout << ps << " --Destructor" << endl;
        else
            cout << "ps is NULL" << " --Destructor" << endl;
        delete[] ps;
    }
    const char * c_str()const { return ps; }
private:
    char *ps;
};

myString::myString(const char *str) {
    if (str == nullptr) {
        ps = new char[1]{ 0 };
        cout << ps << " --Default constructor" << endl;
    }
    else {
        int length = strlen(str);
        ps = new char[length + 1];
        strcpy(ps, str);
        cout << ps << " --Str constructor" << endl;
    }
}

myString::myString(const myString &other) {//拷贝构造
    int length = strlen(other.ps);
    ps = new char[length + 1];
    strcpy(ps, other.ps);
    cout << ps << " --Copy constructor" << endl;
}
myString::myString(myString &&other) noexcept:ps(other.ps)  { //移动构造
    other.ps = nullptr; //要保证:移后源对象能正常析构
    cout << ps << " --Move constructor" << endl;
}
myString &myString::operator=(const myString &other) { //赋值运算符重载
    if (this != &other) {
        delete[] ps;
        int length = strlen(other.ps);
        ps = new char[length + 1];
        strcpy(ps, other.ps);
    }
    cout << ps << " --Copy assignment" << endl;
    return *this;
}

myString &myString::operator=(myString &&other) noexcept {//移动赋值运算符重载
    if (this != &other) {
        delete[] ps;
        ps = other.ps;
        other.ps = nullptr;
    }
    cout << ps << "--Move assignment" << endl;
    return *this;
}

myString operator+(const myString &a, const myString &b) {
    int length = strlen(a.c_str()) + strlen(b.c_str());
    char * p = new char[length + 1];
    strcpy(p, a.c_str());
    strcat(p, b.c_str());
    myString tmp(p);
    delete[] p;
    return tmp;
}

myString fun() {
    myString tmp("allok");
    return tmp;
}
//myString 实现了普通构造,拷贝构造,赋值运算符重载,析构
//         并且实现了 移动构造,移动赋值运算符重载

void fun1(vector<myString> &vs) {
    vs.push_back(myString("abc"));
    //do something...
}
vector<myString> fun2() {
    vector<myString> vs;
    vs.push_back(myString("123"));
    //do something...
    return vs;
}
int main() {
    //没有引入移动语义时,习惯这样用:
    //先准备好 vector, 然后用引用作为参数传递给函数
    vector<myString> vs1;
    fun1(vs1);
    cout << "==========================" << endl;
    //引入移动语义以后,这样写也非常ok
    vector<myString> vs2 = fun2();
    cout << "==========================" << endl;
    return 0;
}

自己编写的移动函数,最好加上noexcept。 vector保证:在调用push_back时发生异常,vector自身不会发生改变。 push_back可能会要求vector重新分配新内存,然后将元素对象从旧内存移动或者拷贝到新内存中。 假如使用拷贝,那么拷贝到一半的时候出现异常的话,由于旧内存的所有内容都还在,所以vector可以很容易恢复到原始状态。 假如使用移动,那么移动到一半的时候出现异常的话,旧空间的部分元素已经被改变了,而新空间中未构造的元素还不存在,此时vector将不能满足自身保持不变的要求。 为了避免这样的情况发生,除非vector知道元素类型的移动函数不会抛出异常,否则在重新分配内存的时候会使用拷贝构造而不是移动构造。 所以,通常将移动构造函数和移动赋值运算符重载标记为 noexcept。

代码语言:javascript
复制
class myString{
    myString(myString &&other) noexcept;//移动构造
  }
myString::myString(myString &&other) noexcept:ps(other.ps){
    other.ps = nullptr;
}

合成的移动函数: 1.自己没有定义拷贝构造、赋值运算符重载和析构函数; 2.类中所有非static数据成员都可移动时;同时满足上面两个条件,编译器会合成默认的移动函数。 移后源对象必须可析构; 移动右值,拷贝左值; 拷贝参数:const T& other 移动参数: T&& other 如果没有移动函数,右值也会被拷贝; std::move 使用move可大幅提高性能,但是要小心使用 move操作,要绝对确认移后源对象没有其他用户。 为了效率,实现移动函数。比如自己写一个String类。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.12.18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引用计数
    • 写时拷贝
      • 对象移动
      • vector动态增长
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档