Développement c/c++, type de classe personnalisé inévitable (Partie 4). Conception des classes et des membres

Développer de bonnes habitudes de conception de classe

Table des matières

1. Variables des participants au régime

        1.1 Assurer l'encapsulation des variables membres

        1.2 Partage de variables avec des classes dérivées

        1.3 Partage de variables avec des fonctions ordinaires ou d'autres fonctions membres de la classe

        1.4 Faire l'ordre de déclaration pour les variables membres

        1.5 imputation pour les variables membres

2. Constructeur et destructeur

        2.1 Les constructeurs et les destructeurs sont toujours activement créés par défaut.

        2.2 Définir activement et explicitement les exigences du constructeur et du destructeur

        2.3 Le destructeur est libéré dans l'ordre inverse de la déclaration des variables

        2.4 Il est recommandé d'utiliser la liste d'initialisation pour initialiser les variables membres

        2.5 Initialiser les variables dans l'ordre de déclaration

3. Opérateur de construction et d'affectation de copies

        3.1 Opérations de construction et d'affectation de copie par défaut

        3.2 Opérations de construction et d'affectation de copies personnalisées

       3.3 Interdiction des opérations de construction et d'affectation de copies

        3.4 Assurez-vous que les variables membres utilisées sont affectées et que l'opération d'affectation renvoie une référence gauche

Quatrièmement, définition du comportement de la fonction membre de la classe

        4.1 Ne vous précipitez pas pour ajouter des fonctions de comportement

        4.2 Planification de nouveaux comportements

        4.3 Surcharger soigneusement les fonctions membres

        4.4 Conflits d'adhésion dans le cadre d'un système d'héritage prudent

Cinq, contraintes d'appartenance à une classe

        5.1 Fonction et application const

        5.2 spécificateur en ligne et variable membre

        5.3 Description des membres statiques - statique

6. Supplément de code source


1. Variables des participants au régime

        1.1 Assurer l'encapsulation des variables membres

        S'il ne s'agit pas d'un développement de projet en langage C, essayez de ne pas utiliser struct pour organiser vos classes, car cela ferait perdre l'encapsulation à vos variables membres. Si vous rendez un membre de données public, tout le monde peut y accéder en lecture et en écriture.

        C'est une bonne habitude de placer les variables membres dans l'autorité d'accès privée et de fournir un accès en lecture seule, en écriture seule et en lecture-écriture à ces variables membres.

class Commit_Access {
public:
    int getreadonly() const{ return readonly; }
    void setreadwrite(int value) { readwrite = value; }
    int getreadwrite() const { return readwrite; }
    void setwriteonly(int value) { writeonly = value; }
private:
    int noaccess;   // 纯内部数据,禁止访问这个 int
    int readonly;   // 可以只读这个 int
    int readwrite;  // 可以读/写这个 int
    int writeonly;  // 可以只写这个 int
};

        1.2 Partage de variables avec des classes dérivées

        Pour les variables membres qui doivent être héritées par les classes dérivées, placez-les dans l'autorisation d'accès protégé et fournissez-les uniquement pour une utilisation partagée par les classes dérivées :

class Commit_Access {
public:
    int getderivedataonly(){return derivedata;}
protected:
    int derivedata; //子类可用
private:

};

class Derived : public Commit_Access
{
private:
    /* data */
    int indata;
public:
    void setpdataonly( int val ){derivedata=val;}   //
};

        1.3 Partage de variables avec des fonctions ordinaires ou d'autres fonctions membres de la classe

        Pour les fonctions ordinaires ou d'autres fonctions membres de classe qui ont besoin d'utiliser des variables membres internes de classe, déclarez-les comme amis. Si d'autres classes n'ont pas la plupart des fonctions membres qui ont besoin d'utiliser les variables membres internes de cette classe, il est recommandé de ne pas déclarer la classe comme amie, mais de déclarer séparément les autres fonctions membres de la classe qui doivent utiliser les variables membres internes comme amies. .

//test1.h
#include <ostream>
class PTest;
class FriendTest
{
private:
    /* data */
public:
    void doit();
    void dosomething(PTest* const obj);
};

class PTest
{
private:
    /* data */
    int val;
public:
    friend std::ostream& operator<<(std::ostream& os, const PTest& obj);
    friend void FriendTest::doit();    //针对成员函数友元而非类
    friend void FriendTest::dosomething(PTest* const obj);
};
//test1.cpp
std::ostream& operator<<(std::ostream& os, const PTest& obj)
{
    os << obj.val;
    return os;
}

void FriendTest::doit()
{
    PTest obj;
    ++obj.val;
};

void FriendTest::dosomething(PTest* const obj)
{
    (obj->val)+=10;
};

        1.4 Faire l'ordre de déclaration pour les variables membres

        S'il y a des variables membres ordinaires et des variables membres dynamiques (variables de pointeur, allocation de mémoire dynamique) dans la classe, dans l'ordre de déclaration, veuillez mettre les déclarations de variables membres ordinaires devant et mettre les variables membres dynamiques à l'arrière, afin que pour faciliter la liste d'initialisation ultérieurement. Pour les variables de membre ordinaires, les variables de membre statiques et les variables de membre const viennent en premier, suivies des variables de type intégrées, puis des variables de membre de type de classe personnalisées. Il en va de même pour les variables de pointeur dynamiques, la variable de pointeur de type intégrée vient en premier et la variable de pointeur de type de classe personnalisée suit.

class A
{
private:
    int val;
public:
};

class B{
    private:
    int val;
public:
};

class C
{
private:
    static int si;
    const char cc = 'D';
    int ival;
    double dval;
    A a;
    char *pc;
    B *pb;
public: 
};

        Pour les variables membres du même type de niveau, il est préférable de placer le type de classe courte en premier et le type de classe longue en dernier, ce qui est bénéfique à la fois du point de vue de l'alignement de la mémoire et de l'efficacité de la construction et de l'initialisation :

class D
{
private:
    bool b1;
    char c1;
    short s1;
    int ival;
    double dval;
    int vec[5];
public:
};

/*不良习惯
class D
{
private:
    double dval;
    bool b1;
    int vec[5];
    char c1;
    int ival;
    short s1; 
public:
};
*/

        1.5 imputation pour les variables membres

        Pour certaines variables étroitement liées, en plus de les regrouper pour une compréhension logique, il est préférable de les fusionner dans un sous-objet pour rassembler ces variables et leur comportement. Par exemple, dans l'exemple suivant, lorsqu'une file d'attente double et un verrou de thread sont utilisés comme variables membres de l'interface d'informations de lecture réseau, ils peuvent être complètement séparés et utilisés comme classe de file d'attente de cache, permettant à la classe de mettre en file d'attente, de sortir de la file d'attente , et la capacité des données Et d'autres comportements sont responsables, la classe d'interface d'informations de lecture réseau utilise uniquement l'objet de file d'attente de cache :

#include <queue>
#include <mutex>

class CacheQue
{
private:
    std::deque<std::string>  msgque;
    std::mutex               msgmutex;
public:
    //func
};
class ReadFromNet
{
private:
    // std::deque<std::string>  msgque;
    // std::mutex               msgmutex;
    //other data
    int readflag;
    int ival;
    //...
    CacheQue msgs;
public:
    //func
};

2. Constructeur et destructeur

        2.1 Les constructeurs et les destructeurs sont toujours activement créés par défaut.

        Presque toutes les classes ont un ou plusieurs constructeurs et un destructeur, qu'ils soient créés explicitement ou par défaut par le compilateur. Parce qu'ils fournissent les fonctions les plus élémentaires. Le constructeur contrôle le fonctionnement de base de l'objet lorsqu'il est généré et s'assure que l'objet est initialisé ; le destructeur détruit un objet et s'assure qu'il est complètement effacé.

        La prise en charge implicite des constructeurs est généralement adoptée pour les variables membres ordinaires qui contiennent des types intégrés standard C++, tels que divers types atomiques, conteneurs, etc.

class Obj1
{
private:
    /* data */
    int id;
    double val;
    std::vector<int> vec;    //OK
    std::string str;         //OK
public:    
    //default func
};

        Par exemple, la variable de membre de type de classe est un handle dynamique et le destructeur défini implicitement ne réalisera pas correctement l'idée du concepteur de type de classe, qui nécessite une définition manuelle du destructeur. Si une classe nécessite un destructeur défini par l'utilisateur, elle nécessite également que l'utilisateur définisse au moins un constructeur.

class Obj2
{
private:
    /* data */
    char* pstr;
public:
    Obj2(){pstr = (char*)malloc(10*sizeof(char));}//手动显式构造函数
    virtual ~Obj2(){delete[] pstr; pstr=nullptr;}         //手动显式析构函数
    //default func
};

        S'il existe un handle dynamique dans la variable membre, il est préférable de déclarer le destructeur en tant que fonction virtuelle virtuelle pour empêcher la classe dérivée de libérer activement la mémoire de la classe de base lorsqu'elle est héritée et utilisée par l'utilisateur.

        2.2 Définir activement et explicitement les exigences du constructeur et du destructeur

        De plus, lorsque le type de classe personnalisé contient les variables membres suivantes, il est également nécessaire de définir explicitement le constructeur et le destructeur :

  • le type T a un membre de données non statique d'un type non-classe const qualifié (ou un tableau de celui-ci);
  • Le type T a des membres de données non statiques de type référence ;
  • Le type T a des membres de données non statiques qui ne peuvent pas être affectés par copie, une classe de base directe ou une classe de base virtuelle ;
  • Un type T est une classe de type union et a un membre variant dont l'opérateur d'affectation de copie correspondant n'est pas trivial.
class Obj3{ private: int ival;};
class Base1{ 
public:
    Base1(){ ptr = new Obj3();};
    virtual ~Base1(){ if(nullptr!=ptr){delete ptr; ptr = nullptr;}};
private: 
    Obj3 *ptr; 
};
class Obj4 : public Base1
{
public:
    //...other
    Obj4(): Base1(),val(0){ ptr = new Obj3(); };          //构造函数
    ~Obj4(){if(nullptr!=ptr){delete ptr; ptr = nullptr;}};//析构函数
private:
    const int a = 10; //
    int val;
    Obj3 *ptr;        //
};

        De plus, si un certain type doit être utilisé comme classe de base, même s'il n'y a pas de variable membre pour l'allocation de mémoire dynamique, il est préférable de déclarer le destructeur en tant que fonction virtuelle virtuelle . Pour plus d'exigences de conception de fonctions et de classes virtuelles, veuillez vous reporter au précédent article de blog de cette colonne (Partie 3).

        2.3 Le destructeur est libéré dans l'ordre inverse de la déclaration des variables

        Lorsque le destructeur libère la mémoire des variables membres dynamiques, il est préférable de la libérer dans l'ordre inverse de l'ordre de déclaration des variables membres :

class Base1{ 
public:
    Base1(){ 
        ptr = new Obj3(); 
        pc = new char[10];
    };
    virtual ~Base1(){ 
        if(nullptr!=pc){delete[] pc; pc = nullptr;}
        if(nullptr!=ptr){delete ptr; ptr = nullptr;}
    };
private: 
    Obj3 *ptr; 
    char *pc;
};

        2.4 Il est recommandé d'utiliser la liste d'initialisation pour initialiser les variables membres

        Essayez d'utiliser l'initialisation au lieu de l'affectation dans le constructeur :

        Les membres de données const et de référence peuvent uniquement être initialisés, pas affectés.

        L'objet membre pointeur peut entraîner une confusion du pointeur ou une interdiction d'affectation lors de l'exécution d'opérations de copie et d'affectation.

        L'initialisation de variables membres avec une liste d'initialisation de membres est plus efficace que l'affectation et l'initialisation de variables membres dans un constructeur, car lors de l'utilisation d'une liste d'initialisation de membres, une seule fonction membre est appelée. Lors de l'attribution d'une valeur dans le constructeur, il y aura deux appels.

class Base2{ 
public:
    Base2(int val){ ptr = new int(val);};
    virtual ~Base2(){ if(nullptr!=ptr){delete ptr; ptr = nullptr;}};
private: 
    Base2(const Base2&) = delete;
    Base2& operator=(const Base2&) = delete;
    int *ptr; 
};

class Obj5
{
public:
    Obj5(const int &ival_, Base2* pc_) : icval(10),ival(ival_),pc(pc_){};
    virtual ~Obj5(){ if(nullptr!=pc){delete pc; pc=nullptr;}};
private:
    const int icval;//必须通过成员初始化列表进行初始化
    int ival;
    const Base2* pc;//必须通过成员初始化列表进行初始化
};

        Même pour un type simple, la duplication inutile d'appels de fonction peut être très coûteuse. Surtout pour les types personnalisés, à mesure que les classes d'extension métier deviennent plus grandes et plus complexes, leurs constructeurs deviennent également plus grands et plus complexes, et le coût de création d'objet devient de plus en plus élevé. Prendre l'habitude d'utiliser des listes d'initialisation de membres chaque fois que possible satisfait non seulement les exigences d'initialisation des membres const et de référence, mais réduit également considérablement les risques d'initialisation inefficace des membres de données. En bref, l'initialisation via la liste d'initialisation des membres est toujours légale, et l'efficacité n'est en aucun cas inférieure à l'affectation dans le corps du constructeur, elle est seulement plus efficace.

        2.5 Initialiser les variables dans l'ordre de déclaration

        Les membres de classe sont initialisés dans l'ordre dans lequel ils sont déclarés dans la classe, quel que soit l'ordre dans lequel ils sont répertoriés dans la liste d'initialisation des membres. De plus, les membres de données de la classe de base sont toujours initialisés avant les membres de données de la classe dérivée. Par conséquent, lors de l'utilisation de l'héritage, l'initialisation de la classe de base doit être répertoriée en haut de la liste d'initialisation des membres.

class Base2{ 
public:
    Base2(int val){ ptr = new int(val);};
    virtual ~Base2(){ if(nullptr!=ptr){delete ptr; ptr = nullptr;}};
private: 
    Base2(const Base2&) = delete;
    Base2& operator=(const Base2&) = delete;
    int *ptr; 
};
class Obj6 : public Base2
{
public: 
    Obj6(const bool& bflag_, const int& ival_, const double& dval_,const int& size=10)
        : Base2(1),bflag(bflag_), ival(ival_), dval(dval_)/*, pc(nullptr)*/
    {
        pc = (char*)malloc(size*sizeof(char));
    };
    virtual ~Obj6(){ if(nullptr!=pc){delete pc; pc=nullptr;}};
private: 
    bool bflag;
    int ival;
    double dval;
    char* pc;
};

class Obj7 : public Base2
{
public: 
    Obj7(const bool& bflag_, const int& ival_, const double& dval_,const int& size=10)
        : ival(ival_), Base2(1), dval(dval_),bflag(bflag_)/*, pc(nullptr)*/
    {
        pc = (char*)malloc(size*sizeof(char));
    };
    virtual ~Obj7(){ if(nullptr!=pc){delete pc; pc=nullptr;}};
private: 
    bool bflag;
    int ival;
    double dval;
    char* pc;
};
//
#include <iostream>
#include <chrono>
const unsigned long sizel = 10000;
void test1(void)
{
    auto start = std::chrono::system_clock::now();
    for (size_t row = 0; row < sizel; row++)
    for (size_t i = 0; i < sizel; i++)
    {
        Obj6 obj6(i/2,i%2,i*1.0);
    }
    auto end = std::chrono::system_clock::now();
    std::chrono::duration<double,std::milli> diff = end-start;
    std::cout << "Obj6 diff.count() = " <<  diff.count() << "ms\n";
    
    start = std::chrono::system_clock::now();
    for (size_t row = 0; row < sizel; row++)
    for (size_t i = 0; i < sizel; i++)
    {
        Obj7 obj7(i/2,i%2,i*1.0);
    }
    end = std::chrono::system_clock::now();
    diff = end-start;
    std::cout << "Obj7 diff.count() = " <<  diff.count() << "ms\n";
};
//
D:\workForMy\workspace\class_test4_c++>test.exe
Obj6 diff.count() = 13927.5ms
Obj7 diff.count() = 13970.9ms

        Dans le code ci-dessus, Base2 sera toujours initialisé en premier (bien sûr, il peut aussi être implicitement initialisé), suivi de bflag, iva, dvall et pc. Pour tous les membres d'un objet, leurs destructeurs sont toujours appelés dans l'ordre inverse dans lequel ils ont été créés dans le constructeur. Ensuite, s'il n'est pas initialisé dans l'ordre de déclaration, le compilateur doit suivre l'ordre dans lequel ses membres sont initialisés pour chaque objet afin de s'assurer que leurs destructeurs sont appelés dans le bon ordre. Cela vient avec des frais généraux coûteux. Par conséquent, afin d'éviter cette surcharge, le compilateur traitera les membres dans le même ordre dans le processus de création (construction) et de destruction (destruction) de tous les objets du même type, quels que soient les membres de la liste d'initialisation. quel ordre.

        Regardez à nouveau l'exemple suivant, initialisez d'abord la taille, puis utilisez la taille pour initialiser le vecteur, mais le résultat souhaité n'est pas obtenu.

#include <vector>
#include <iostream>

class Obj8
{
public:   
    Obj8(int size_) :size(size_), ivec(size){};
    ~Obj8(){};
    void test(){
        std::cout << "size = "<<size<<"\n";
        std::cout << "ivec.size = "<<ivec.size()<<"\n";
    }
private: 
    std::vector<int> ivec;
    int size;
};
void test1(void)
{
    Obj8 obj8(10);
    obj8.test();
};
//out log
size = 10
ivec.size = 6290744    //为啥不是10呢

        En effet, les membres de la classe sont initialisés dans l'ordre dans lequel ils sont déclarés dans la classe, ce qui n'a rien à voir avec l'ordre dans lequel ils sont répertoriés dans la liste d'initialisation des membres, c'est-à-dire que vector est initialisé en premier et une valeur indéterminée est passé, donc vector Il n'a pas attendu que size soit initialisé en premier, mais il a été initialisé avant size. Le code ci-dessus n'est correct que si l'ordre de déclaration des deux membres est permuté.

//交换声明次序,其他不变    
int size;
std::vector<int> ivec; 
//out log
size = 10
ivec.size = 10

3. Opérateur de construction et d'affectation de copies

        3.1 Opérations de construction et d'affectation de copie par défaut

        Pour les variables non dynamiques dont les variables membres sont des types intégrés, bien que le constructeur de copie par défaut et la fonction d'affectation de copie puissent être utilisés, il est recommandé d'utiliser le mot clé par défaut pour marquer explicitement :

#include <string>
class Obj9
{
public:
    Obj9() = default;
    Obj9(const Obj9&) = default;
    Obj9& operator=(const Obj9&) = default;
private:    
    bool bval;
    int ival;
    std::string str;
};
//
    Obj9 Obj9_1;
    Obj9 Obj9_2(Obj9_1);
    Obj9 Obj9_3;
    Obj9_2 = Obj9_1;

        3.2 Opérations de construction et d'affectation de copies personnalisées

        Pour les classes qui doivent allouer dynamiquement de la mémoire, déclarez explicitement un constructeur de copie et un opérateur d'affectation ou interdisez explicitement la copie et l'affectation .

        Parce que pour une classe qui alloue dynamiquement de la mémoire, si le constructeur de copie et l'opérateur d'affectation ne sont pas clairement définis ou interdits, lors de l'utilisation de l'appel, par exemple, si la classe a une variable char*, utilisez operator=, C++ générera et appellera un opérateur par défaut = opérateur. L'opérateur d'affectation par défaut effectue une affectation membre par membre d'un membre à un membre de b, qui est une copie au niveau du bit pour les pointeurs (a.data et b.data). Il est possible que la mémoire pointée par b ne soit jamais supprimée, elle sera donc perdue à jamais, ce qui entraînera une fuite de mémoire. Ou maintenant les pointeurs contenus dans a et b pointent sur la même chaîne, donc tant que l'un d'eux quitte son espace vital, son destructeur supprimera la mémoire vers laquelle l'autre pointeur pointe encore. La deuxième exception se produit également lors de l'utilisation du constructeur de copie.

//test1.h
class MyString
{
public:
	MyString(void);							//默认构造函数
	MyString( const char *str = nullptr );	//普通构造函数
	MyString( const MyString &other );		//拷贝构造函数
	~MyString( void );						//析构函数
	MyString& operator=(const MyString &other);	//赋值函数
    MyString& operator=(const char* other);	    //赋值函数
	char* c_str(void) const;					//取值(取值)
private:
    void init(const char *str);
    void copy(const char *str);
private:
	char *m_data;
};
//test1.cpp
#include <cstring>
//默认构造函数
MyString::MyString(void)
{
	MyString(nullptr);	//内部调用普通构造函数
}
//普通构造函数
MyString::MyString(const char *str)
{
    init(str);
}
// MyString 的析构函数
MyString::~MyString(void)
{
	delete [] m_data; // 或 delete m_data;
}

void MyString::init(const char *str)
{
	if(nullptr==str)
	{
		m_data = new char[1];	//对空字符串自动申请存放结束标志'\0'的空
		*m_data = '\0';
	}else{
		int length = strlen(str);
		m_data = new char[length+1]; // 分配内存
		strcpy(m_data, str);
	}
};

void MyString::copy(const char *str)
{
    if(nullptr!=str)
    {
        delete [] m_data;				//释放原有的内存资源
        int length = strlen( str );
        m_data = new char[length+1];	//重新分配内存
        strcpy( m_data, str);
    }
};

//拷贝构造函数
MyString::MyString( const MyString &other ) //输入参数为const型
{
    init(other.m_data);
}

//赋值函数
MyString &MyString::operator =( const MyString &other )//输入参数为const型
{
	if(this == &other)				//检查自赋值
		return *this;	
    copy(other.m_data);	
	return *this;					//返回本对象的引用
}

MyString& MyString::operator=(const char* other)	    //赋值函数
{
    copy(other);	
    return *this;					//返回本对象的引用
};

char* MyString::c_str(void) const
{
	return (char*)m_data;
}
//
    MyString mstr1("hello");
    MyString mstr2("");
    mstr2 = mstr1;
    MyString mstr3 = mstr2;
    mstr3 = "world";

       3.3 Interdiction des opérations de construction et d'affectation de copies

         Si vous n'avez pas besoin d'interdire les fonctions de construction de copie et d'affectation de copie, interdisez-les explicitement , de la manière suivante :

  • Déclarez les fonctions de construction de copie et d'affectation de copie en tant que fonctions privées, qui ne peuvent être utilisées que par des fonctions qui déclarent des amis ou des fonctions membres internes.
  • Déclarez les fonctions de construction de copie et d'affectation de copie en tant que fonctions privées et définissez-les vides. Bien qu'elles puissent être appelées, elles ne sont pas valides.
  • Déclarez les fonctions de construction de copie et d'affectation de copie en tant que fonctions privées et utilisez le mot-clé delete pour supprimer explicitement, et tout appel déclenchera une alarme lors de la compilation.
class Obj10
{
public:
    Obj10()= default;
private:
    //方式一,自定义提供拷贝构造及复制赋值
    // Obj10(const Obj10&){/*code*/};
    // Obj10& operator=(const Obj10&){/*code*/return *this;};
    //方式二,提供默认拷贝构造及复制赋值
    // Obj10(const Obj10&) =default;
    // Obj10& operator=(const Obj10&) = default;
    //方式三,自定义提供空的拷贝构造及复制赋值
    // Obj10(const Obj10&){/*不做任何处理*/};
    // Obj10& operator=(const Obj10&){/*不做任何处理*/return *this;};
    //方式四,强制删除拷贝构造及复制赋值,禁止任何函数调用
    Obj10(const Obj10&) = delete;
    Obj10& operator=(const Obj10&) = delete;
private:    
    bool bval;
    int ival;
    std::string str;
    char* pc;
};

        3.4 Assurez-vous que les variables membres utilisées sont affectées et que l'opération d'affectation renvoie une référence gauche

        Étant donné que l'associativité de l'opérateur d'affectation va de droite à gauche, la valeur de retour de operator= doit être acceptée par la fonction elle-même comme paramètre d'entrée, suivant le principe selon lequel l'entrée et le retour de operator= sont tous des références à des objets de classe. Lors de la définition de votre propre opérateur d'affectation, vous devez renvoyer la référence "*this" du paramètre de gauche de l'opérateur d'affectation. Si vous ne le faites pas, l'affectation continue ne sera pas possible, ou la conversion de type implicite au moment de l'appel, ou les deux.

        Dans le même temps, il convient de noter que lors de la définition du constructeur de copie et de l'opérateur d'affectation, assurez-vous que tous les membres de données sont affectés, en particulier lors de l'ajout de nouveaux membres de données à la classe, n'oubliez pas de mettre à jour le constructeur de copie et la fonction d'opérateur d'affectation.

        Lorsque l'utilisateur appelle le type, il se rend compte de la situation de s'assigner (obj1=obj1, obj1=obj2 mais obj2 est un alias de obj1), il faut donc s'assurer que l'assignation à lui-même est cochée dans la fonction operator=. Parce qu'un opérateur d'affectation doit d'abord libérer les ressources d'un objet (supprimer l'ancienne valeur), puis allouer de nouvelles ressources en fonction de la nouvelle valeur. Dans le cas de l'auto-affectation, il serait désastreux de libérer l'ancienne ressource, car l'ancienne ressource serait nécessaire lorsque la nouvelle ressource serait allouée.

class Obj11
{
public:
    Obj11(const int& ival_, const double& dval_=0.0) 
    : ival(ival_),dval(dval_){ };
    Obj11(const Obj11& obj){
        ival = obj.ival;
        dval = obj.dval;
    };
    Obj11& operator=(const Obj11& obj){
        if(this==&obj) return *this;
        ival = obj.ival;
        dval = obj.dval;
        return *this;
    };
private:    
    int ival;
    double dval;
};
//
    Obj11 Obj11_1(1), Obj11_2(2), Obj11_3(3), Obj11_4(4);
    Obj11_1 = Obj11_2 = Obj11_3 = Obj11_4;
    (Obj11_1 = Obj11_2) = Obj11_3 = Obj11_4;
    Obj11_1 = (Obj11_2 = Obj11_3) = Obj11_4;

        Définissez l'opérateur= du type Obj11 pour renvoyer une référence à "*this", qui peut implémenter des opérations d'affectation continue (chaîne) similaires aux types intégrés.

Quatrièmement, définition du comportement de la fonction membre de la classe

        4.1 Ne vous précipitez pas pour ajouter des fonctions de comportement

        Habituellement, les fonctions membres d'une classe sont divisées en scénarios d'application, qui sont les fonctions membres de base de la classe et les fonctions membres du comportement de classe.

        Les fonctions membres de base de classe sont les constructeurs, les destructeurs, les constructeurs de copie et les fonctions d'affectation de copie les plus élémentaires décrits ci-dessus ; les fonctions membres de comportement de classe sont principalement basées sur les comportements uniques fournis par les variables membres de type pour les applications métier, telles que diverses opérations. Fonctions d'opérateur telles que construction de copie de déplacement, affectation de déplacement, fonction d'appel de classe, opérateurs d'affectation généraux, opérateurs arithmétiques, opérateurs IO, etc., ainsi que la réalisation de comportements complexes tels que la recherche, le tri, les opérations sur les caractères et le traitement primitif.

        La conception initiale des fonctions membres d'une classe doit inclure une définition implicite ou explicite par défaut du constructeur (un ou plusieurs), un destructeur, l'utilisation explicite ou l'interdiction du constructeur de copie et l'opérateur d'affectation de copie.

        Par exemple, la classe MyString définie ci-dessus implémente deux constructeurs, un destructeur, un constructeur de copie et deux fonctions d'opérateur d'affectation. Elle fournit des fonctions de fonction intégrées pour aider à la construction et aux fonctions d'opération d'affectation, et fournit enfin une fonction d'acquisition. fonction valeur de la variable privée :

class MyString
{
public:
	MyString(void);							//无参数构造函数,通过调用普通构造函数来实现的
	MyString( const char *str = nullptr );	//普通构造函数,通过调用了内置init函数实现
	MyString( const MyString &other );		//拷贝构造函数,也调用了内置init函数实现
	~MyString( void );						//一个析构函数
	MyString& operator=(const MyString &other);	//拷贝赋值函数,MyString到MyString,调用了内置copy函数实现
    MyString& operator=(const char* other);	    //转换赋值函数,char*到MyString,调用了内置copy函数实现
	char* c_str(void) const;					//取值(取值)
private:
    void init(const char *str);
    void copy(const char *str);
private:
	char *m_data;
};

        Dans le code ci-dessus, l'interface de type MyString permet aux utilisateurs de créer des objets avec des paramètres vides ou des pointeurs de caractères, de supprimer des objets, de copier des objets, de transformer des pointeurs de caractères en objets et d'obtenir des pointeurs de variables de caractères pour utiliser le contenu de stockage.

        La conception de la classe doit suivre l'ajout progressif de fonctions membres comportementales selon les besoins, fournir une interface qui satisfait toute chose raisonnable que l'entreprise de scène actuelle veut faire, et insister sur une interface avec aussi peu de fonctions que possible, et pas deux fonctions se superposent.

        Lors de l'ajout d'une nouvelle fonction à une interface, réfléchissez attentivement : ajouter une nouvelle fonction en partant du principe que l'interface est complète, si la commodité qu'elle apporte dépasse le coût supplémentaire qu'elle apporte, comme la complexité, la lisibilité et la maintenabilité, le sexe et le temps de compilation, etc. . Par exemple, une fonctionnalité commune serait plus efficacement implémentée en tant que fonction membre, ce qui serait une bonne raison de l'ajouter à une interface. L'ajout d'une fonction membre peut empêcher les erreurs de l'utilisateur et constitue également une base solide pour l'ajouter à l'interface.

        4.2 Planification de nouveaux comportements

        Un nouveau comportement ajouté est qu'il doit être précisé s'il est fourni en tant que fonction membre, fonction normale ou fonction amie. S'il s'agit d'une fonction membre, quel type de droits d'accès doit être fourni et s'il doit à déclarer en tant que fonction virtuelle.

        C'est une bonne habitude de dériver le positionnement des nouveaux comportements sous la forme de scénarios d'exigences applicatives. Par exemple, prenez MyString comme exemple. Si vous devez prendre en charge la sortie vers ostream pour l'affichage, si vous positionnez "operator<<" en tant que fonction membre, bien que l'effet de la sortie vers ostream puisse également être obtenu, les habitudes d'utilisation sont incohérentes avec la bibliothèque standard : l'une consiste à envoyer le contenu "hello" à l'ostream, et l'autre à envoyer le contenu "hello" à l'ostream.

class MyString
{
public:
	//other
    std::ostream& operator<<(std::ostream& output){
        output << std::string(m_data);
        return output;
    };
    //other
private:
	char *m_data;
};
//
    MyString mstr1("hello");
    mstr1 << std::cout; //OK
    // std::cout << mstr1; //error
//out log
hello

        Cependant, si operator<< est positionné comme une fonction normale et prise en charge par des amis, alors ses habitudes de sortie seront cohérentes avec la bibliothèque standard, et il sera plus pratique de sortir le contenu "hello" depuis ostream en termes de comportement.

class MyString
{
public:
    //other
    friend std::ostream& operator<<(std::ostream& output,const MyString& obj){
        output << std::string(obj.m_data);
        return output;
    };
    //other
private:
	char *m_data;
};
//
    MyString mstr1("hello");
    //mstr1 << std::cout; //
    std::cout << mstr1; //OK
//out log 
hello

        4.3 Surcharger soigneusement les fonctions membres

        Lorsque les fonctions membres ont le même nom, le même nombre de paramètres formels et presque le même contenu d'implémentation, veuillez déterminer si les paramètres réels par défaut et la conversion de type peuvent répondre aux exigences pour économiser une longue conception de surcharge.

class Obj12
{
public:
    void func(){ std::cout<<"func()\n"; };
    void func(const char& sval_){std::cout<<"func(const char&)\n"; };
    void func(const int& ival_){std::cout<<"func(const int&)\n"; };
    void g(const int& ival_=0){std::cout<<"g(const int&)\n";};
private:
    int ival;
};
//
    Obj12 obj12;
    obj12.func();
    obj12.func('a');
    obj12.func(0);
    obj12.func(10);
    obj12.g();
    obj12.g('a');
    obj12.g(0);
    obj12.g(10);  
//out log
func()
func(const char&)
func(const int&)
func(const int&)
g(const int&)
g(const int&)
g(const int&)
g(const int&)

        4.4 Conflits d'adhésion dans le cadre d'un système d'héritage prudent

        Dans le système d'héritage multiple, en particulier le système d'héritage diamant, il y a souvent des conflits de membres, et il est nécessaire de concevoir avec soin la méthode d'héritage et la définition des membres de la classe. Une attention particulière doit être accordée à la combinaison de l'héritage multiple et des fonctions virtuelles. Pour cet aspect, veuillez vous référer au troisième article de blog "Classes et fonctions virtuelles" de ce sujet dans cette colonne. Il y a trop d'élaborations ici.

class Base3 {
public:
    int doIt(){return 3;};// 一个叫做int doIt 的函数
};
class Base4
{
public:
    void doIt(){};// 一个叫做void doIt 的函数
};
class Derived1: public Base3, public Base4 { public: };

class Derived2: public Base3{};
class Derived3: public Base3{};
class Derived4: public Derived2,public Derived3{};
//
    Derived1 d1;
    // d1.doIt();         // 错误!——二义
    // int i1 = d1.doIt();// 错误!——二义
    Derived4 d4;
    // d4.doIt();            // 错误!——二义

        Plusieurs fois, de nombreuses classes sont conçues et le programme peut être utilisé lorsqu'il n'y a pas d'ambiguïté d'utilisation. Cette ambiguïté potentielle ne peut pas être découverte à temps, elle peut être latente dans le programme pendant longtemps, non détectée et inactive, jusqu'à ce qu'un certain moment critique vienne.

Cinq, contraintes d'appartenance à une classe

        5.1 Fonction et application const

        Le mot-clé const est largement utilisé dans les classes. À l'intérieur d'une classe, il peut être utilisé pour les membres statiques et non statiques. Pour les pointeurs, le pointeur lui-même peut être spécifié comme const, les données pointées par le pointeur peuvent également être spécifiées comme const, ou les deux peuvent être spécifiés comme const en même temps. Dans une déclaration de fonction, const peut faire référence à la valeur de retour de la fonction ou à un paramètre ; pour les fonctions membres, il peut également faire référence à la fonction entière.

        Nous utilisons généralement const plus "passer par référence" au lieu de "passer par valeur" pour définir les paramètres de la fonction membre. Le passage d'un objet par valeur conduit finalement à appeler un constructeur de copie de type d'objet, tandis que le passage de paramètres par référence évite de construire une nouvelle copie du paramètre et économise la surcharge. De plus, lorsqu'un objet d'une classe dérivée est passé par valeur en tant qu'objet de classe de base, ses caractéristiques de comportement (objet de classe dérivée) en tant que classe dérivée seront "coupées", devenant ainsi un simple objet de classe de base. référence pour éviter les problèmes de coupe.

class X
{
public:
    int getVal(void) const{return ival;}
    void setVal(const int& ival_){
        ival=ival_;
        // ival_ += 1; //error
    };
    friend const X operator+(const X& lhs,const X& rhs){
        X ret(lhs);
        ret.ival+=rhs.ival;
        return ret;
    };
    friend X operator-(const X& lhs,const X& rhs){
        X ret(lhs);
        ret.ival-=rhs.ival;
        return ret;
    };
private:
    int ival;
};
//
    X x1,x2;
    x1.setVal(10);
    x2.setVal(20);
    (x1-x2)=x1; //OK
    //(x1+x2)=x1; //error
    x1=x1+x2;   //OK 

        Normalement, le retour d'une fonction membre utilisant des contraintes const ne peut pas être modifié. Toute fonction qui ne modifie pas les données membres doit être déclarée const. Si vous modifiez accidentellement les données membres ou appelez d'autres fonctions membres non const lors de l'écriture de fonctions membres const, le compilateur signalera l'erreur, ce qui améliorera sans aucun doute la robustesse du programme. const Pour la déclaration const d'une fonction membre, le mot-clé const ne peut être placé qu'à la fin de la déclaration de la fonction. Pour les fonctions ordinaires et les fonctions amies, le mot-clé const est placé au début de la déclaration de la fonction.

        Dans une fonction membre const, le type de this est un pointeur const vers un objet de type de classe const. Ni l'objet pointé par this ni l'adresse enregistrée par this ne peuvent être modifiés. Si vous ajoutez une décoration const à la valeur de retour de la fonction en mode "passage de pointeur", le contenu de la valeur de retour de la fonction (c'est-à-dire le pointeur) ne peut pas être modifié et la valeur de retour ne peut être affectée qu'au même type de pointeur avec const décoration. Placer const après la fonction restreint principalement les fonctions membres de la classe, et placer const avant la fonction restreint la modification de la valeur de retour via le pointeur lorsque le type de retour de la fonction est un pointeur.

        Avec const, vous pouvez informer le compilateur et les autres programmeurs qu'une valeur doit rester inchangée. Chaque fois que c'est le cas, vous devez utiliser const explicitement, car cela permet au compilateur de s'assurer que la contrainte n'est pas violée. Cependant, parfois, l'application de contraintes const au retour est également mal calculée. Certaines fonctions membres qui n'obéissent pas à la définition de la contrainte const peuvent également réussir le test de compilation. Par exemple, dans l'exemple suivant, une instruction d'exécution qui "modifie les données pointées par le pointeur (opérateur de fonction membre char*() const)" viole évidemment la contrainte const (pchar1[0] = 'a';), mais quand la compilation passera.

class MyString
{
public:
    //other
    operator char*() const;                     //MyString转char*
	char* c_str(void) const;					//取值(取值)
    //other
private:
	char *m_data;
};
//test1.cpp
MyString::operator char*() const
{
    return (char*)m_data;
};

char* MyString::c_str(void) const
{
	return (char*)m_data;
};
//
    MyString mstr1("hello\n");
    std::cout << mstr1;    //OK
    char *pchar1 = mstr1;  //operator char*() const
    pchar1[0] = 'a';
    pchar1 = nullptr;
    std::cout << mstr1;   //OK,输出aello
    char *pchar2 = mstr1.c_str(); //
    // pchar2[0] = "b";   //error,不能将 "const char *" 类型的值分配到 "char" 类型的实体
    std::cout << mstr1;   //OK

        Bien sûr, n'abandonnez pas le renvoi d'objets à cause de certains avantages des fonctions renvoyant des références, et n'essayez pas de renvoyer une référence lorsque vous devez renvoyer un objet. Habituellement, pour les fonctions membres, la plupart d'entre elles sont implémentées en renvoyant des références, tandis que pour les fonctions amies, la plupart d'entre elles sont implémentées en renvoyant des objets.

Obj& Obj::operator-=(const Obj& rhs)
{
    ival -= rhs.ival;
    return *this;
}

Obj operator-(const Obj& lhs)
{
    Objret(lhs);
    ret.ival = -ret.ival;
    return ret;
}

        5.2 inlineSpécificateurs et variables membres

        Le but du spécificateur en ligne est d'inviter le compilateur à effectuer des optimisations, telles que l'intégration de fonctions, qui nécessitent généralement que le compilateur voie la définition de la fonction. Les compilateurs peuvent (et le font souvent) ignorer la présence ou l'absence du spécificateur en ligne à des fins d'optimisation. Si le compilateur inline une fonction, il remplace tous ses appels par le corps de la fonction pour éviter la surcharge des appels de fonction (mettre des données sur la pile et obtenir des résultats), ce qui peut entraîner un exécutable plus volumineux, car la fonction peut être répétée plusieurs fois. fois. Le résultat est le même qu'une macro de type fonction, sauf que l'identifiant et la macro utilisés pour la fonction font référence à la définition vue au point de définition, et non à la définition au point d'appel.

*inline 函数说明符,在用于函数的声明说明符序列时,将函数声明为一个内联(inline)函数。

1)完全在 class/struct/union 的定义之内定义,且被附着到全局模块 (C++20 起)的函数是隐式的
内联函数,无论它是成员函数还是非成员 friend 函数。

2)(C++11 起)声明有 constexpr 的函数是隐式的内联函数。弃置的函数是隐式的内联函数:其(弃置)
定义可出现在多于一个翻译单元中。
  
3)(C++17 起)inline 说明符,在用于具有静态存储期的变量(静态类成员或命名空间作用域变量)的
声明说明符序列时,将变量声明为内联变量。声明为 constexpr 的静态成员变量(但不是命名空间
作用域变量)是隐式的内联变量。

在内联函数中,
*所有函数定义中的函数局部静态对象在所有翻译单元间共享。
*所有函数定义中所定义的类型同样在所有翻译单元中相同。

 (C++17 起) 关键词 inline 对于函数的含义已经变为“容许多次定义”而不是“优先内联”,
因此这个含义也扩展到了变量。

        En C++, si une fonction est déclarée inline, elle doit être déclarée inline dans chaque unité de traduction, et chaque fonction inline doit avoir exactement la même définition). C++, d'autre part, autorise les objets statiques locaux à la fonction non const, et tous les objets statiques locaux à la fonction provenant de différentes définitions d'une fonction en ligne sont identiques. Les fonctions membres générées implicitement, et toutes les fonctions membres déclarées comme prédéfinies dans leur première déclaration, sont en ligne comme toute autre fonction définie dans une définition de classe.

class InlineTest
{
public:
    void inl(int& ival_){};          //inline隐式
    inline void f(int& ival_){};     //inline显式
    void g(char& cval_);
    constexpr void func(double& dval);//inline隐式,(C++11 起)
private:
    inline static int n = 1;    //inline变量
};
//test1.cpp
inline void InlineTest::g(char& cval_){};       //inline显式
constexpr void InlineTest::func(double& dval){};//inline隐式

        Qu'elles soient inline ou non, les fonctions inline garantissent la sémantique suivante : toute fonction avec liaison interne peut être déclarée static inline, sans aucune autre restriction. Une fonction en ligne non statique ne peut pas définir un objet statique local de fonction non const et ne peut pas utiliser d'objets statiques de portée de fichier.

        5.3 静态成员Description - statique

        Les membres statiques d'une classe ne sont pas associés aux objets de la classe : ce sont des variables indépendantes avec une durée de stockage statique ou de thread (depuis C++11), ou des fonctions régulières. Le mot-clé static ne sera utilisé que dans la déclaration d'un membre statique dans la définition de classe, pas dans la définition du membre statique.

class X { private:static int n; }; // 声明(用 'static')
int X::n = 1;              // 定义(不用 'static')

        Fonction membre statique :

  • Les fonctions membres statiques ne sont associées à aucun objet. Lorsqu'ils sont invoqués, ils n'ont pas de pointeur this.
  • Les fonctions membres statiques ne peuvent pas être virtuelles, constantes ou volatiles.
  • L'adresse d'une fonction membre statique peut être stockée dans un pointeur de fonction normal, mais pas dans un pointeur de fonction membre.

        Membres de données statiques :

  • Les membres de données statiques ne sont associés à aucun objet. Ils existent même si aucun objet de la classe n'est défini. Il n'y a qu'une seule instance d'un membre de données statique avec une durée de stockage statique dans tout le programme, à moins que le mot-clé thread_local ne soit utilisé, auquel cas chaque thread a une instance de cet objet avec une durée de stockage de thread (depuis C++11).
  • Les membres de données statiques ne peuvent pas être modifiables.
  • Au niveau de l'espace de noms, si la classe elle-même a une liaison externe (c'est-à-dire qu'elle n'est pas membre d'un espace de noms sans nom), les données membres statiques de la classe ont également une liaison externe. Les classes locales (classes définies à l'intérieur des fonctions) et les classes sans nom, y compris les classes membres de classes sans nom, ne peuvent pas avoir de données membres statiques.
  • Les membres de données statiques peuvent être déclarés en ligne. Les membres de données statiques en ligne peuvent être définis dans une définition de classe et des initialiseurs peuvent être affectés.
  • Si une donnée membre statique de type intégral ou énumération est déclarée const (et non volatile), elle peut être initialisée directement dans la définition de classe avec un initialiseur dans lequel chaque expression est une expression constante
  • Si un membre de données statique d'un type littéral (LiteralType) est déclaré constexpr, il doit être initialisé directement dans la définition de classe avec un initialiseur dans lequel chaque expression est une expression constante
  • Si un membre de données statique const non inline (depuis C++17) ou un membre de données statique constexpr (depuis C++11) (avant C++17) est utilisé dans le sens ODR, alors la définition de portée d'espace de noms est toujours requis, mais il ne peut pas y avoir d'initialiseur. Une définition peut être fournie, bien que celle-ci soit redondante (depuis C++17).
  • Si un membre de données statiques est déclaré constexpr, il est implicitement en ligne et n'a pas besoin d'être redéclaré au niveau de l'espace de noms. Cette redéclaration sans initialiseur (obligatoire auparavant, comme indiqué ci-dessus) est toujours autorisée, mais est obsolète (depuis C++17).
class Obj13
{
public:
    static void f(int ival_);
private:
    inline static int i = 1;
    const static int n = 1;
    const static int m{2}; // C++11 起
    const static int k;
    constexpr static int arr[] = { 1, 2, 3 };        // OK C++11 起
    // constexpr static int l; // 错误:constexpr static 要求初始化器
    constexpr static int l{1}; // OK C++11 起
};
//test1.cpp
void Obj13::f(int ival_){};
const int Obj13::k = 3;

6. Supplément de code source

        Instruction de compilation g++ main.cpp test*.cpp -o test.exe -std=c++17 (inline agit sur les variables membres)

        main.cpp

#include "test1.h"

int main(int argc, char* argv[])
{
    test1();
    return 0;
}

        test1.h

#ifndef _TEST_1_H_
#define _TEST_1_H_

class Commit_Access {
public:
    int getreadonly() const{ return readonly; }
    void setreadwrite(int value) { readwrite = value; }
    int getreadwrite() const { return readwrite; }
    void setwriteonly(int value) { writeonly = value; }
    int getderivedataonly(){return derivedata;}
protected:
    int derivedata; //子类可用
private:
    int noaccess;   // 纯内部数据,禁止访问这个 int
    int readonly;   // 可以只读这个 int
    int readwrite;  // 可以读/写这个 int
    int writeonly;  // 可以只写这个 int
};

class Derived : public Commit_Access
{
private:
    /* data */
    int indata;
public:
    void setpdataonly( int val ){derivedata=val;}   //
};

#include <ostream>
class PTest;
class FriendTest
{
private:
    /* data */
public:
    void doit();
    void dosomething(PTest* const obj);
};

class PTest
{
private:
    /* data */
    int val;
public:
    friend std::ostream& operator<<(std::ostream& os, const PTest& obj);
    friend void FriendTest::doit();
    friend void FriendTest::dosomething(PTest* const obj);
};

class A
{
private:
    int val;
public:
};

class B{
    private:
    int val;
public:
};

class C
{
private:
    static int si;
    const char cc = 'D';
    int ival;
    double dval;
    A a;
    char *pc;
    B *pb;
public: 
};

class D
{
private:
    bool b1;
    char c1;
    short s1;
    int ival;
    double dval;
    int vec[5];
public:
};

/*
class D
{
private:
    double dval;
    bool b1;
    int vec[5];
    char c1;
    int ival;
    short s1; 
public:
};
*/
#include <queue>
#include <mutex>

class CacheQue
{
private:
    std::deque<std::string>  msgque;
    std::mutex               msgmutex;
public:
    //func
};
class ReadFromNet
{
private:
    // std::deque<std::string>  msgque;
    // std::mutex               msgmutex;
    //other data
    int readflag;
    int ival;
    //...
    CacheQue msgs;
public:
    //func
};

class Obj1
{
private:
    /* data */
    int id;
    double val;
    std::vector<int> vec;    //OK
    std::string str;         //OK
public:    
    //default func
};

class Obj2
{
private:
    /* data */
    char* pstr;
public:
    Obj2(){pstr = (char*)malloc(10*sizeof(char));}//手动显式构造函数
    virtual ~Obj2(){delete[] pstr; pstr=nullptr;}         //手动显式析构函数
    //default func
};

class Obj3{ private: int ival;};
class Base1{ 
public:
    Base1(){ 
        ptr = new Obj3(); 
        pc = new char[10];
    };
    virtual ~Base1(){ 
        if(nullptr!=pc){delete[] pc; pc = nullptr;}
        if(nullptr!=ptr){delete ptr; ptr = nullptr;}
    };
private: 
    Obj3 *ptr; 
    char *pc;
};
class Obj4 : public Base1
{
public:
    //...other
    Obj4(): Base1(),val(0){ ptr = new Obj3(); }; //构造函数
    ~Obj4(){if(nullptr!=ptr){delete ptr; ptr = nullptr;}};// 析构函数
private:
    const int a = 10; //
    int val;
    Obj3 *ptr;        //
};

class Base2{ 
public:
    Base2(int val){ ptr = new int(val);};
    virtual ~Base2(){ if(nullptr!=ptr){delete ptr; ptr = nullptr;}};
private: 
    Base2(const Base2&) = delete;
    Base2& operator=(const Base2&) = delete;
    int *ptr; 
};

class Obj5
{
public:
    Obj5(const int &ival_, Base2* pc_) : icval(10),ival(ival_),pc(pc_){};
    virtual ~Obj5(){ if(nullptr!=pc){delete pc; pc=nullptr;}};
private:
    const int icval;//必须通过成员初始化列表进行初始化
    int ival;
    const Base2* pc;//必须通过成员初始化列表进行初始化
};

class Obj6 : public Base2
{
public: 
    Obj6(const bool& bflag_, const int& ival_, const double& dval_,const int& size=10)
        : Base2(1),bflag(bflag_), ival(ival_), dval(dval_)/*, pc(nullptr)*/
    {
        pc = (char*)malloc(size*sizeof(char));
    };
    virtual ~Obj6(){ if(nullptr!=pc){delete pc; pc=nullptr;}};
private: 
    bool bflag;
    int ival;
    double dval;
    char* pc;
};

class Obj7 : public Base2
{
public: 
    Obj7(const bool& bflag_, const int& ival_, const double& dval_,const int& size=10)
        : ival(ival_), Base2(1), dval(dval_),bflag(bflag_)/*, pc(nullptr)*/
    {
        pc = (char*)malloc(size*sizeof(char));
    };
    virtual ~Obj7(){ if(nullptr!=pc){delete pc; pc=nullptr;}};
private: 
    bool bflag;
    int ival;
    double dval;
    char* pc;
};

#include <vector>
#include <iostream>

class Obj8
{
public:   
    Obj8(int size_) :size(size_), ivec(size){};
    ~Obj8(){};
    void test(){
        std::cout << "size = "<<size<<"\n";
        std::cout << "ivec.size = "<<ivec.size()<<"\n";
    }
private: 
    int size;
    std::vector<int> ivec; 
};

#include <string>
class Obj9
{
public:
    Obj9()= default;
    Obj9(const Obj9&) = default;
    Obj9& operator=(const Obj9&) = default;
private:    
    bool bval;
    int ival;
    std::string str;
};

// #include <ostream>
class MyString
{
public:
	MyString(void);							//默认构造函数
	MyString( const char *str = nullptr );	//普通构造函数
	MyString( const MyString &other );		//拷贝构造函数
	~MyString( void );						//析构函数
	MyString& operator=(const MyString &other);	//赋值函数
    MyString& operator=(const char* other);	    //赋值函数
    operator char*() const;                     //MyString转char*
	char* c_str(void) const;					//取值(取值)

    std::ostream& operator<<(std::ostream& output){
        output << std::string(m_data);
        return output;
    };
    friend std::ostream& operator<<(std::ostream& output,const MyString& obj){
        output << std::string(obj.m_data);
        return output;
    };
private:
    void init(const char *str);
    void copy(const char *str);
private:
	char *m_data;
};

class Obj10
{
public:
    Obj10()= default;
private:
    //方式一,自定义提供拷贝构造及复制赋值
    // Obj10(const Obj10&){/*code*/};
    // Obj10& operator=(const Obj10&){/*code*/return *this;};
    //方式二,提供默认拷贝构造及复制赋值
    // Obj10(const Obj10&) =default;
    // Obj10& operator=(const Obj10&) = default;
    //方式三,自定义提供空的拷贝构造及复制赋值
    // Obj10(const Obj10&){/*不做任何处理*/};
    // Obj10& operator=(const Obj10&){/*不做任何处理*/return *this;};
    //方式四,强制删除拷贝构造及复制赋值,禁止任何函数调用
    Obj10(const Obj10&) = delete;
    Obj10& operator=(const Obj10&) = delete;
private:    
    bool bval;
    int ival;
    std::string str;
    char* pc;
};

class Obj11
{
public:
    Obj11(const int& ival_, const double& dval_=0.0) 
    : ival(ival_),dval(dval_){ };
    Obj11(const Obj11& obj){
        ival = obj.ival;
        dval = obj.dval;
    };
    Obj11& operator=(const Obj11& obj){
        if(this==&obj) return *this;
        ival = obj.ival;
        dval = obj.dval;
        return *this;
    };
private:    
    int ival;
    double dval;
};

class X
{
public:
    int getVal(void) const{return ival;}
    void setVal(const int& ival_){
        ival=ival_;
        // ival_ += 1; //error
    };
    friend const X operator+(const X& lhs,const X& rhs){
        X ret(lhs);
        ret.ival+=rhs.ival;
        return ret;
    };
    friend X operator-(const X& lhs,const X& rhs){
        X ret(lhs);
        ret.ival-=rhs.ival;
        return ret;
    };
private:
    int ival;
};

class Obj12
{
public:
    void func(){ std::cout<<"func()\n"; };
    void func(const char& sval_){std::cout<<"func(const char&)\n"; };
    void func(const int& ival_){std::cout<<"func(const int&)\n"; };
    void g(const int& ival_=0){std::cout<<"g(const int&)\n";};
private:
    int ival;
};

class Base3 {
public:
    int doIt(){return 3;};// 一个叫做int doIt 的函数
};
class Base4
{
public:
    void doIt(){};// 一个叫做void doIt 的函数
};
class Derived1: public Base3, public Base4 { public: };

class Derived2: public Base3{};
class Derived3: public Base3{};
class Derived4: public Derived2,public Derived3{};

class InlineTest
{
public:
    void inl(int& ival_){};          //inline隐式
    inline void f(int& ival_){};     //inline显式
    void g(char& cval_);
    constexpr void func(double& dval);//inline隐式,(C++11 起)
private:
    inline static int n = 1;    //inline变量
};

class Obj13
{
public:
    static void f(int ival_);
private:
    inline static int i = 1;
    const static int n = 1;
    const static int m{2}; // C++11 起
    const static int k;
    constexpr static int arr[] = { 1, 2, 3 };        // OK C++11 起
    // constexpr static int l; // 错误:constexpr static 要求初始化器
    constexpr static int l{1}; // OK C++11 起
};

void test1(void);

#endif //_TEST_1_H_

        test1.cpp

#include "test1.h"

std::ostream& operator<<(std::ostream& os, const PTest& obj)
{
    os << obj.val;
    return os;
}

void FriendTest::doit()
{
    PTest obj;
    ++obj.val;
};

void FriendTest::dosomething(PTest* const obj)
{
    (obj->val)+=10;
};

#include <cstring>
//默认构造函数
MyString::MyString(void)
{
	MyString(nullptr);	//内部调用普通构造函数
}
//普通构造函数
MyString::MyString(const char *str)
{
    init(str);
}
// MyString 的析构函数
MyString::~MyString(void)
{
	delete [] m_data; // 或 delete m_data;
}

void MyString::init(const char *str)
{
	if(nullptr==str)
	{
		m_data = new char[1];	//对空字符串自动申请存放结束标志'\0'的空
		*m_data = '\0';
	}else{
		int length = strlen(str);
		m_data = new char[length+1]; // 分配内存
		strcpy(m_data, str);
	}
};

void MyString::copy(const char *str)
{
    if(nullptr!=str)
    {
        delete [] m_data;				//释放原有的内存资源
        int length = strlen( str );
        m_data = new char[length+1];	//重新分配内存
        strcpy( m_data, str);
    }
};

//拷贝构造函数
MyString::MyString( const MyString &other ) //输入参数为const型
{
    init(other.m_data);
}

//赋值函数
MyString &MyString::operator =( const MyString &other )//输入参数为const型
{
	if(this == &other)				//检查自赋值
		return *this;	
    copy(other.m_data);	
	return *this;					//返回本对象的引用
}

MyString& MyString::operator=(const char* other)	    //赋值函数
{
    copy(other);	
    return *this;					//返回本对象的引用
};

MyString::operator char*() const
{
    return (char*)m_data;
};

char* MyString::c_str(void) const
{
	return (char*)m_data;
};

#include <iostream>
#include <chrono>
const unsigned long sizel = 10000;
void initialization_test(void)
{
   auto start = std::chrono::system_clock::now();
    for (size_t row = 0; row < sizel/10; row++)
    for (size_t i = 0; i < sizel; i++)
    {
        Obj6 obj6(i/2,i%2,i*1.0);
    }
    auto end = std::chrono::system_clock::now();
    std::chrono::duration<double,std::milli> diff = end-start;
    std::cout << "Obj6 diff.count() = " <<  diff.count() << "ms\n";
    
    start = std::chrono::system_clock::now();
    for (size_t row = 0; row < sizel/10; row++)
    for (size_t i = 0; i < sizel; i++)
    {
        Obj7 obj7(i/2,i%2,i*1.0);
    }
    end = std::chrono::system_clock::now();
    diff = end-start;
    std::cout << "Obj7 diff.count() = " <<  diff.count() << "ms\n";
}
//
inline void InlineTest::g(char& cval_){};       //inline显式
constexpr void InlineTest::func(double& dval){};//inline隐式
//
void Obj13::f(int ival_){};
const int Obj13::k = 3;

void test1(void)
{
    Obj5 obj5(1,new Base2(1));
    //
    // initialization_test();
    //
    Obj8 obj8(10);
    obj8.test();
    //
    Obj9 Obj9_1;
    Obj9 Obj9_2(Obj9_1);
    Obj9 Obj9_3;
    Obj9_2 = Obj9_1;
    //
    MyString mstr1("hello\n");
    mstr1 << std::cout; //OK,不建议
    std::cout << mstr1; //OK
    char *pchar1 = mstr1;       //operator char*() const
    pchar1[0] = 'a';
    pchar1 = nullptr;
    std::cout << mstr1; //OK
    char *pchar2 = mstr1.c_str();
    // pchar2[0] = "b";    //error,不能将 "const char *" 类型的值分配到 "char" 类型的实体
    std::cout << mstr1; //OK
    MyString mstr2("");
    mstr2 = mstr1;
    MyString mstr3 = mstr2;
    mstr3 = "world";
    //
    Obj11 Obj11_1(1), Obj11_2(2), Obj11_3(3), Obj11_4(4);
    Obj11_1 = Obj11_2 = Obj11_3 = Obj11_4;
    (Obj11_1 = Obj11_2) = Obj11_3 = Obj11_4;
    Obj11_1 = (Obj11_2 = Obj11_3) = Obj11_4;
    //
    X x1,x2;
    x1.setVal(10);
    x2.setVal(20);
    (x1-x2)=x1; //OK
    // (x1+x2)=x1; //error
    x1=x1+x2;   //OK 
    //
    Obj12 obj12;
    obj12.func();
    obj12.func('a');
    obj12.func(0);
    obj12.func(10);
    obj12.g();
    obj12.g('a');
    obj12.g(0);
    obj12.g(10); 
    //
    Derived1 d1;
    // d1.doIt();         // 错误!——二义
    // int i1 = d1.doIt();// 错误!——二义
    Derived4 d4;
    // d4.doIt();            // 错误!——二义
};

Je suppose que tu aimes

Origine blog.csdn.net/py8105/article/details/129634949
conseillé
Classement