The fundamental ideas behind classes are data abstraction and encapsulation.
-
前置
++
与后置++
从效率上来讲,若是内置数据类型,二者效率相同;但若是非内置数据类型,则前置效率更高,因为前置直接返回修改后自身的引用,而后置则需要将原来的赋值给一个临时变量,修改自身,最后再返回该临时变量。大致如下: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; } // 返回类型的不同与二者的工作逻辑有关
-
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.
-
Member functions defined outside the class should add
ClassName::
before the function name, to indicate that they are in the scope of the class. -
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.
-
It’s not always necessary for the class to hide the implementation, sometimes it’s better to expose it. e.g the
pair
type. -
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.
-
类成员设为
private
,可以避免外部对该成员的直接操控。这样在需要对该成员改动的时候,不会影响到外部代码,同时在 debug 的时候也不用考虑外部因素。这也是封装隔离的好处。 -
在类内部实现的函数自动被视为
inline
,此外自己加个inline
关键词到类内函数声明或类外函数定义处也可以实现内联,注意内联成员函数必须和类声明写在同一个头文件中。 -
类的声明:
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; };
-
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.greturn *this;
实现成员函数连续调用。要注意,连续调用时,const 函数后面不可调用 non-const 的函数。解决问题是函数重载,即重载该 const 函数(去掉 const )。这实际上相当于重载函数中的参数是否 const 的重载。
-
A mutable data member is a member that is never const, can be modified by a const member function, by a const object.
-
多于一处出现相同目的/作用的代码,将其写成一个函数来调用,避免重复多处修改、方便调试等等。
-
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) {}
-
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.
-
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
-
系统生成的默认构造函数会调用类成员的默认构造函数,而对于内置类型或复合类型(比如指针,数组等),则要看具体的对象在哪里定义——若是全局范围,则会被初始化为
0, nullptr, ""
等等;若是局部范围,则是未初始化(当然可能也和编译器有关,但总体上说是不可靠的)。因此,若类中含有内置/复合类型,则不能指望系统生成的默认构造函数,应当自己写一个。
A constructor that does not initialize a member of built-in or compound type leaves that member in an undefined state.
-
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
的,这样可以防止很多错误发生。 -
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();} };
-
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;}
-
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(评注版)