C++ Primer 总结之Chap12 Classes

本文总结了C++ Primer中关于类的核心概念,包括数据抽象、封装、类的成员、构造函数、默认构造函数、静态成员、隐式类型转换以及友元。讨论了数据成员的访问控制,强调了构造函数在初始化工作中的重要性,以及如何通过初始化列表避免成员依赖。同时,解释了静态成员函数与非静态成员函数的区别,并探讨了隐式类型转换的潜在问题和友元的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

The fundamental ideas behind classes are data abstraction and encapsulation.

  1. 前置++与后置++从效率上来讲,若是内置数据类型,二者效率相同;但若是非内置数据类型,则前置效率更高,因为前置直接返回修改后自身的引用,而后置则需要将原来的赋值给一个临时变量,修改自身,最后再返回该临时变量。大致如下:

    Class MyClass{
        
      MyClass& operator ++(); // 前置
      MyClass operator ++(int); // 后置,int无实际意义,仅作区分前后置所用
    };
    
    MyClass& MyClass::operator ++(){
        // Some operations
        return *this;
    }
    
    const MyClass MyClass::operator ++(int){
        MyClass tmp = *this;
        ++(*this); // 故前置需写在后置前面
        return tmp;
    }
    
    // 返回类型的不同与二者的工作逻辑有关
    
  2. Each class defines 0 or more members, which can be either data, functions, or type definitions. All members must be declared inside the class; no way to add members once the class definition is complete.

  3. Member functions defined outside the class should add ClassName:: before the function name, to indicate that they are in the scope of the class.

  4. Data abstraction is a programming and design technique that relies on the separation of interface and implementation. It should let the programmers using the class understand by the interface but need not to care about how it is implemented concretely.

    Encapsulation is a term that describes the technique of combining lower-level elements to form a new, higher-level entity.

  5. It’s not always necessary for the class to hide the implementation, sometimes it’s better to expose it. e.g the pair type.

  6. Users of the classes we design are programmers, so we should try to meet the needs of them, think about what they care about and not. Good class designers define a class interface that is intuitive and easy to use.

  7. 类成员设为private,可以避免外部对该成员的直接操控。这样在需要对该成员改动的时候,不会影响到外部代码,同时在 debug 的时候也不用考虑外部因素。这也是封装隔离的好处。

  8. 在类内部实现的函数自动被视为inline,此外自己加个inline关键词到类内函数声明或类外函数定义处也可以实现内联,注意内联成员函数必须和类声明写在同一个头文件中。

  9. 类的声明:class ClassName; 有时候被叫做 forward declaration,在声明之后定义之前的位置,这个类是一个不完整类型(incomplete type),sizeof 结果为0。不可定义此类的对象,可以用来定义指向该类的引用或指针,或者用作其他函数的声明中的返回类型或参数(定义不可用它)。

    A common use of class forward declaration is to write classes that are mutually dependent on one another.

    class Y;
    class X {
        Y *y;
    };
    
    class Y {
        X x;
    };
    
  10. this是成员函数隐藏的第一个参数,it is const pointer in non-const member function and const pointer to const in const member function. It’s used when we need to refer to the whole object, e.g return *this;实现成员函数连续调用。

    要注意,连续调用时,const 函数后面不可调用 non-const 的函数。解决问题是函数重载,即重载该 const 函数(去掉 const )。这实际上相当于重载函数中的参数是否 const 的重载。

  11. A mutable data member is a member that is never const, can be modified by a const member function, by a const object.

  12. 多于一处出现相同目的/作用的代码,将其写成一个函数来调用,避免重复多处修改、方便调试等等。

  13. Constructor is used to do the initialization work, and it’s better to do it with Constructor Initializer instead of inside the constructor body.

    Note that initialization is seemed to be done before the execution of constructor body. So members of a class type without default constructor and members that are const or reference types must be initialized in the constructor initializer regardless of type. (We should initialize const objects or objects of reference type but can’t assign to them.)

    // e.g
    
    class A{ // have no defaut constructor
    public:
        A(int a){}    
    };
    
    class B{
    public:
        B(int tmp);
    private:
        int b;
        const int cb;
        int &rb;
    }
    
    // This constructor cannot compile
    B::B(int tmp){
    // seen as a general computation phase, not initialization phase
        b = tmp; // ok
        cb = tmp; // error: cannot assign to a const
        rb = b; // error
    }
    
    // Correct version
    B::B(int tmp): b(tmp), cb(tmp), rb(b)
        {}
    
    
  14. The order of member initialization has nothing to do with the order in constructor initializer, but theorder in which the members are defined.

    class X{
        int i;
        int j;
    public:
        X(int val): j(val), i(j){} 
        // error, i will be initialized with the as yet uninitialized value  of j
    };
    

    It’s a good idea to write constructor initializers in the same order as the members are declared. Moreover, avoid using members to initialize other members to get rid of dependencies when possible.

  15. If a class defines even one constructor, no matter it is default or not, the compiler will not generate the default constrcutor for that class any more. Moreover, neither for classes that have members of that class.

    Non-default type may not be used as the element type for a dynamically allocated array.

    Statically allocated arrays of Non-defalut type must provide an explicit initializer for each element.

    So make sure your classes have a default constructor.

    // Ways to call default constructor
    
    ClassName objName(); // a common mistake, this is a function!
    ClassName objName = ClassName(); // ok
    ClassName objName; // ok
    
  16. 系统生成的默认构造函数会调用类成员的默认构造函数,而对于内置类型或复合类型(比如指针,数组等),则要看具体的对象在哪里定义——若是全局范围,则会被初始化为0, nullptr, ""等等;若是局部范围,则是未初始化(当然可能也和编译器有关,但总体上说是不可靠的)。

    因此,若类中含有内置/复合类型,则不能指望系统生成的默认构造函数,应当自己写一个。

    A constructor that does not initialize a member of built-in or compound type leaves that member in an undefined state.

  17. Implicit Class-Type Conversions: A constructor with a single argument(not parameter) defines an implicit conversion from the parameter type to the class type.

    class A{
    public:
        A(){}
        A(int i){}
        void f(A a){}
    };
    A tmp;
    tmp.f(1); // implicit conversion happens
    

    这个可能会方便但也可能会引发很多问题,比如增加了函数重载的复杂性。

    void g( string a,  string b) { cout << "a\n"; }
    void g( string a, bool b) { cout << "b\n"; }
    
    g("a", "b"); // output : b, what we did not expect
    

    如上例所示,隐式转换的优先级较低,在用"a"的时候其实也是在隐式地调用string的构造函数。这里如果要调用第一个函数只有显式地调用构造函数:g(string("a"), string("b");

    解决方法是在该默认构造函数前面加上explicit,注意在类外定义处不必也不可以加。此后便不能隐式地构造对象了。

    通常情况下,单实参的构造函数都应该是explicit的,这样可以防止很多错误发生。

  18. Friends

    声明为某个类的friend之后,可以使用该类的非共有成员。具体声明为friend的可以是类、普通的函数、类的具体某个成员函数等。最后一个存在声明先后顺序互相依赖的问题(个人目前暂时还没去找如何解决)

    A friend declaration introduces the named class or nonmember function into the surrounding scope.

    class X{
        friend class Y;
        friend void f() {/* ok to define friend function in the class body */}
    };
    
    class Z{
        Y *y; // ok: declaration for Y has been introduced by friend in X
        void g() {return ::f();}
    };
    
  19. static 静态成员与类同时存在一份,与类的对象没有归属关系。静态成员函数不可调用非静态成员函数。

    The keyword static appears only with the declaration inside the class.

    Static member funtions can be defined inside or outside the class body, but for static data members, they must be declared inside the class, defined and initialized outside the class.

    One exception is static const data member of integral type, which can be initialized within the class body by a constant expression. But still it may be required to be defined outside the class without an initial value.(Some compilers do not require it now)

    // A.h
    class A{
    public:
        
        static void f(){}
        
    private:
        static int a;
        static const int b = 10;
        double c[b]; // ok: b is constant expression
        
        static int g();
    };
    
    // A.cpp
    int A::a = g();
    const int A::b;
    int A::g(){return 10;}
    
    
  20. A static data member can be of the type that it belongs to. A nonstatic data member is restricted to being declared as a pointer or reference to an object of its own class.

    class Point{
    public:
        // ...
    private:
        static Point p1; // ok
        Point p2; // error
        Point *p3; // ok
        
    };
    

Reference : C++ Primer 4th edition(评注版)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值