本笔记基于中国大学慕课《C/C++语言程序设计(下)(沈萦华老师) 》及「OpenJudge - 信通学院编程小组 」。
一、命名空间
——避免命名冲突。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 namespace A { int x, y; } void fun1 () { x = 8 ; A::y = 6 ; } void fun2 () { using namespace A; x = 8 ; y = 6 ; } void fun3 () { using namespace A::y; x = 8 ; y = 6 ; }
二、引用 引用 即别名:
1 2 3 4 5 6 const int maxN = 3e5 + 8 ;int n = 1 , m = 2 ;int & a = n; int & b; b = n; int & c = n; c = &m; int & d = maxN;
例 1.1 调用传递引用的参数,实现两个字符串变量的交换。
答
1 2 3 4 5 6 7 8 void swap (char *& a, char *& b) { char * tmp = a; a = b; b = tmp; } int main () { char * ap = (char *)"hello" ; char * bp = (char *)"how are you?" ; swap (ap, bp); }
评注 在 C 语言中,需使用
1 2 3 4 5 6 7 8 void swap (char ** a, char ** b) { char * tmp = *a; *a = *b; *b = tmp; } int main () { char * ap = (char *)"hello" ; char * bp = (char *)"how are you?" ; swap(&ap, &bp); }
常引用 常引用不可修改:
1 2 3 4 5 6 7 int n = 8 ;const int & a = n;a = 6 ; n = 6 ; int & b = a; int & c = (int &)a; const int & b = a;
三、函数 内联函数 inline 功能 任何调用函数的地方都换成了其代码。
作用 避免因为频繁大量的使用栈空间,而造成因栈空间不足所造成的程式出错的问题,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。
代码风格 它是用于实现的关键字:
1 2 3 4 5 6 7 8 9 inline void fun1 (int n) ;void fun1 (int n) { } void fun2 (int n) ;inline void fun2 (int n) { }
适用情形 只适合函数体内代码简单的函数数使用:
不能包含复杂的结构控制语句例如 while
、switch
;
内联函数本身不能是直接递归函数。
不适用情形 内联是以代码膨胀为代价,因此以下情况不宜使用内联:
如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
函数重载 名字相同但参数个数或参数类型不同的函数:
1 2 3 4 5 6 7 8 9 int max (double f1, double f2) { } int max (int n1, int n2) { } int max (int n1, int n2, int n3) { } int main () { max (3.4 , 2.5 ); max (2 , 4 ); max (1 , 2 , 3 ); max (3 , 2.4 ); }
带默认参数的函数(函数的缺省参数) 作用 提高程序的可扩充性。
1 2 3 4 5 6 7 8 9 int fun (int x = 1 , int y = 2 ) ;int main () { cout << fun (6 , 8 ); cout << fun (6 ); cout << fun (); } int fun (int x, int y) { return x + y; }
注意 在指定了默认值的参数的右边,不能出现没有指定默认值的参数。只能最右边连续的几个参数缺省。
传值、传址与传引用 例 2 修改程序。将其修改为传引用参数,并修改 findmax()
为非递归函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void findmax (int * a, int n, int i, int * pk) ;int main () { int a[10 ], n = 0 ; for (int i = 0 ; i < 10 ; i++) cin >> a[i]; findmax (a, 10 , 0 , &n); } void findmax (int * a, int n, int i, int * pk) { if (i < n) { if (a[i] > a[*pk]) *pk = i; findmax (a, n, i + 1 , &(*pk)); } }
答
1 2 3 4 5 6 7 8 9 10 11 12 13 void findmax (int * a, int n, int i, int & pk) ;int main () { int a[10 ], n = 0 ; for (int i = 0 ; i < 10 ; i++) cin >> a[i]; findmax (a, 10 , 0 , n); } void findmax (int * a, int n, int i, int & pk) { for (int i = 0 ; i < n; ++i) { if (a[i] > a[pk]) pk = i; } }
例 3 分析代码,思考哪些可以实现两数交换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void swap1 (int a, int b) { int temp = a; a = b; b = temp; } void swap2 (int * a, int * b) { int temp = *a; *a = *b; *b = temp; } void swap3 (int * a, int * b) { int * temp = a; a = b; b = temp; } void swap4 (int & a, int & b) { int temp = a; a = b; b = temp; }
函数返回引用 函数作为左值:
1 2 3 4 5 6 7 8 9 int mx;int & max (int a, int b) { mx = a > b ? a : b; return mx; } int main () { int a = 6 , b = 8 ; cout << max (a, b); }
例 4 生成有 10 个整数的安全数组。要把值放入数组中,使用 put()
函数,然后使用 get()
取出该值。
答
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int vals[10 ];int error = -1 ;int & put (int n) { if (n >= 0 && n < 10 ) return vals[n]; cout << "RE" ; return error; } int get (int n) { if (n >= 0 && n < 10 ) return vals[n]; return error; } int main () { int n; cin >> n; if (put (n) == -1 ) return 0 ; cin >> put (n); cout << get (n) << endl; return 0 ; }
例 5
1 2 3 4 5 6 7 8 9 int & fun (int a) { static int t = 0 ; return t += 2 * a; } int main () { int x = 1 , y = 2 ; cout << ++fun (x); cout << ++fun (y); }
四、类的基本概念 下列关键字说明类成员可被访问的范围:
1 2 3 private : public : protected :
在类的成员函数内部,能够访问当前对象和同类其它对象的全部属性、函数;在类的成员函数以外的地方,只能够访问该类对象的公有成员。
使用类的成员变量和成员函数:
基类的 private 成员:
类的 public 成员:
基类的成员函数
基类的友元函数
派生类的成员函数
派生类的友元函数
其他的函数
基类的 protected 成员:
基类的成员函数
基类的友元函数
派生类的成员函数可以访问当前对象和其它对象的基类的保护成员
五、构造函数与析构函数
构造函数 初始化成员变量。构造函数需 public;构造函数名与类名相同;可定义不同的构造函数以初始化不同情形。
拷贝构造函数 X::X(X& other)
当用类对象初始化另一对象、以类为参数调用函数或函数返回类时调用。在赋值时不起作用。
类型转换构造函数 实现类型的自动转换。只有一个参数,而且不是复制构造函数的构造函数,一般看作转换构造函数。
析构函数 在对象消亡(包括程序结束、函数参数消亡、在函数中作为临时对象返回)时调用,可用于 delete
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class mymyClass { private : int value; string name; public : mymyClass (int val, const string& n) : value (val), name (n) {} mymyClass (const mymyClass& other) : value (other.value), name (other.name) {} mymyClass (int val) : value (val), name ("Default" ) {} ~mymyClass () {} }; int main () { mymyClass obj1 (8 , "Obj1" ) ; mymyClass obj2 = obj1; mymyClass obj3 (8 ) ; }
六、静态成员 static ——为所有对象共享。
静态成员本质上是 全局变量 。
静态成员函数 不具体作用于某个对象 ,因此静态成员不需要通过对象就能访问。
在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。
sizeof
不会计算静态成员变量。
七、this 指针
当你进入 3427 后,你可以看见桌游、火锅等,但是你看不到 3427 的全貌了。 但 this 指针时时刻刻指向着 3427 本身。
——指向被调用的成员函数所属的对象。
1 2 3 4 5 6 7 8 9 10 11 class myClass {public : int num; myClass (int num) { this ->num = num; } myClass& add (myClass other) { this ->num += other.num; return *this ; } };
this 指针的本质是一个 指针常量 。
每个非静态成员函数中都隐含着 this 指针,因此类的非静态成员函数,真实的参数比所写的参数多 1。
this 指针 只能在成员函数中使用,不能在全局函数、静态成员函数中使用 (因为静态成员函数并不具体作用与某个对象)。
this 指针和函数的其他参数生命周期一样,在成员函数的开始前构造,并在成员函数的结束后清除。
八、封闭类 ——包含成员对象(一个类的成员变量是另一个类的对象)的类。
封闭类对象生成时,先执行 所有对象成员的构造函数 ,然后才执行 封闭类的构造函数 ,其中,对象成员的构造函数调用次序 与对象成员在类中的说明次序 一致,与它们在成员初始化列表中出现的次序无关。
当封闭类的对象消亡时,先执行 封闭类的析构函数 ,然后再执行 成员对象的析构函数 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class myClassA { private : string name; public : myClassA (const string& s) : name (s) {} void print () const { cout << name << endl; } }; class myClassB { private : int val; public : myClassB (int value) : val (value) {} void print () const { cout << val << endl; } }; class myEnclosedClass { private : myClassA classA; myClassB classB; public : myEnclosedClass (const string& name, int val) : classA (name), classB (val) {} void printAll () const { classA.print (); classB.print (); } }; int main () { myEnclosedClass obj ("Default" , 8 ) ; obj.printAll (); }
九、友元 friend 友元函数 一个类的友元函数可以访问该类的私有成员:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class myClassA ; class myClassB { public : void displaynum (const myClassA& obj) ; }; class myClassA { private : int num; public : myClassA (int value) : num (value) {} friend void myClassB::displaynum (const myClassA& obj) ; }; void myClassB::displaynum (const myClassA& obj) { cout << "myClassA 的私有成员变量 num 的值为: " << obj.num << endl; } int main () { myClassA a (8 ) ; myClassB b; b.displaynum (a); return 0 ; }
友元类 如果 A 是 B 的友元类,那么 A 的成员函数可以访问B的私有成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class myClass { private : int num; public : myClass (int value) : num (value) {} friend class myFriendmyClass ; }; class myFriendmyClass { public : void displaynum (myClass& obj) { cout << obj.num << endl; } }; int main () { myClass obj (8 ) ; myFriendmyClass friendobj; friendobj.displaynum (obj); }
十、常量成员函数 const ——不能改变属性的值,也不能调用非常量成员函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class myClass {private : int num; mutable val; public : void fun1 () {} void fun2 () const { num = 0 ; val = 0 ; fun (); } void fun2 () {} }; int main () { const myClass obj; obj.fun1 (); obj.fun2 (); }
十一、重载运算符 重载运算符 1 2 3 4 5 6 7 8 9 10 11 class Complex { double Re, Im; Complex (double r = 0.0 , double i = 0.0 ) :Re (r), Im (i) {} Complex operator -(const Complex& b); }; Complex operator +(const Complex& a, const Complex& b) { return Complex (a.Re + b.Re, a.Im + b.Im); } Complex Complex::operator -(const Complex& b) { return Complex (Re - b.Re, Im - b.Im); }
不允许定义新的运算符;
对运算符进行重载的时候,应尽量保留运算符原本的特性;
".", ".*", "::", "?:", "sizeof"
不能被重载;
重载运算符 "()", "[]", "->", "="
时,必须声明为类的成员函数。
重载赋值运算符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class myString {private : char * str; public : myString () :str (new char [1 ]) { str[0 ] = 0 ; } const char * c_str () { return str; }; myString& operator = (const char * s); ~myString () { delete [] str; } }; myString& myString::operator = (const char * s) { delete [] str; str = new char [strlen (s) + 1 ]; strcpy (str, s); return *this ; } int main () { myString s; s = "Hello" ; cout << s.c_str () << endl; return 0 ; }
浅拷贝和深拷贝 以上代码(浅拷贝,只是简单地复制了原对象的指针值)中,执行 s1=s2
后,s1.str
和 s2.str
指向同一地方,因此需要补充拷贝构造函数的重载(深拷贝,分配新的内存来存储原对象的数据并复制原对象的数据到新分配的内存中):
1 2 3 4 5 6 myString& operator = (const myString& s) { delete [] str; str = new char [strlen (s.str) + 1 ]; strcpy (str, s.str); return *this ; }
但为避免 s=s
,需进一步补充:
1 2 3 4 5 6 7 myString& operator = (const myString& s) { if (this == &s) return *this ; delete [] str; str = new char [strlen (s.str) + 1 ]; strcpy (str, s.str); return *this ; }
编写拷贝构造函数的时候,会面临和 =
同样的问题,用同样的方法处理。
1 2 3 4 myString (myString& s) { str = new char [strlen (s.str) + 1 ]; strcpy (str, s.str); }
总的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 #include <cstdlib> #include <iostream> using namespace std;int strlen (const char * s) { int i = 0 ; for (; s[i]; ++i); return i; } void strcpy (char * d, const char * s) { int i = 0 ; for (i = 0 ; s[i]; ++i) d[i] = s[i]; d[i] = 0 ; } int strcmp (const char * s1, const char * s2) { for (int i = 0 ; s1[i] && s2[i]; ++i) { if (s1[i] < s2[i]) return -1 ; else if (s1[i] > s2[i]) return 1 ; } return 0 ; } void strcat (char * d, const char * s) { int len = strlen (d); strcpy (d + len, s); } class MyString {private : char * str; public : MyString (const char * s = NULL ) { if (s) { str = new char [strlen (s) + 1 ]; strcpy (str, s); } else { str = new char [1 ]; str[0 ] = '\0' ; } } ~MyString () { if (str) delete [] str; } MyString (const MyString& s) { if (s.str) { str = new char [strlen (s.str) + 1 ]; strcpy (str, s.str); } else { str = new char [1 ]; str[0 ] = '\0' ; } } MyString& operator = (const char * s) { if (str == s) { return *this ; } delete [] str; if (s) { str = new char [strlen (s) + 1 ]; strcpy (str, s); } else { str = new char [1 ]; str[0 ] = '\0' ; } return *this ; } MyString& operator =(const MyString& s) { if (str == s.str) { return *this ; } delete [] str; if (s.str) { str = new char [strlen (s.str) + 1 ]; strcpy (str, s.str); } else { str = new char [1 ]; str[0 ] = '\0' ; } return *this ; } friend MyString operator + (const MyString& a, const MyString& b) { char * tmp = new char [strlen (a.str) + strlen (b.str) + 1 ]; strcpy (tmp, a.str); strcat (tmp, b.str); return MyString (tmp); } friend ostream& operator << (ostream& o, const MyString& s) { o << s.str; return o; } char & operator [] (int i) { return str[i]; } MyString& operator += (const char * s) { char * tmp = new char [strlen (str) + 1 ]; strcpy (tmp, str); delete [] str; str = new char [strlen (tmp) + strlen (s) + 1 ]; strcpy (str, tmp); strcat (str, s); return *this ; } friend MyString operator + (const char * p, const MyString& s) { char * tmp = new char [strlen (p) + strlen (s.str) + 1 ]; strcpy (tmp, p); strcat (tmp, s.str); return MyString (tmp); } friend MyString operator + (const MyString& s, const char * p) { char * tmp = new char [strlen (s.str) + strlen (p) + 1 ]; strcpy (tmp, s.str); strcat (tmp, p); return MyString (tmp); } friend bool operator < (const MyString& s1, const MyString& s2) { if (strcmp (s1.str, s2.str) < 0 ) { return true ; } else { return false ; } } friend bool operator > (const MyString& s1, const MyString& s2) { if (strcmp (s1.str, s2.str) > 0 ) return true ; else return false ; } friend bool operator == (const MyString& s1, const MyString& s2) { if (strcmp (s1.str, s2.str) == 0 ) return true ; else return false ; } char * operator () (int start, int length) { char * tmp = new char [length + 1 ]; for (int i = start; i < start + length; ++i) { tmp[i - start] = str[i]; } tmp[length] = '\0' ; return tmp; } }; int CompareString (const void * e1, const void * e2) { MyString* s1 = (MyString*)e1; MyString* s2 = (MyString*)e2; if (*s1 < *s2) return -1 ; else if (*s1 == *s2) return 0 ; else if (*s1 > *s2) return 1 ; } int main () { MyString s1 ("abcd-" ) , s2, s3 ("efgh-" ) , s4 (s1) ; MyString SArray[4 ] = { "big" ,"me" ,"about" ,"take" }; cout << "1. " << s1 << s2 << s3 << s4 << endl; s4 = s3; s3 = s1 + s3; cout << "2. " << s1 << endl; cout << "3. " << s2 << endl; cout << "4. " << s3 << endl; cout << "5. " << s4 << endl; cout << "6. " << s1[2 ] << endl; s2 = s1; s1 = "ijkl-" ; s1[2 ] = 'A' ; cout << "7. " << s2 << endl; cout << "8. " << s1 << endl; s1 += "mnop" ; cout << "9. " << s1 << endl; s4 = "qrst-" + s2; cout << "10. " << s4 << endl; s1 = s2 + s4 + " uvw " + "xyz" ; cout << "11. " << s1 << endl; qsort (SArray, 4 , sizeof (MyString), CompareString); for (int i = 0 ; i < 4 ; i++) cout << SArray[i] << endl; cout << s1 (0 , 4 ) << endl; cout << s1 (5 , 10 ) << endl; return 0 ; }
重载为友元函数 一般情况下,将运算符重载为类的成员函数,是较好的选择。但有时,重载为成员函数不能满足使用要求,重载为普通函数,又不能访问类的私有成员,所以需要将运算符重载为友元。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Complex { double Re, Im; public : Complex (double r, double i) :Re (r), Im (i) {}; Complex operator +(double r); friend Complex operator + (double r, const Complex& c); }; Complex Complex::operator +(double r) { return Complex (Re + r, Im); } Complex operator + (double r, const Complex& c) { return Complex (c.Re + r, c.Im); }
重载流插入运算符和流提取运算符 使用 ostream& ostream::operator<<(int n)
重载流插入运算符 <<
,流提取运算符 >>
同理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Complex { double Re, Im; public : Complex (double r = 0 , double i = 0 ) :Re (r), Im (i) {}; friend ostream& operator <<(ostream& os, const Complex& c); friend istream& operator >>(istream& is, Complex& c); }; ostream& operator <<(ostream& os, const Complex& c) { os << c.Re << "+" << c.Im << "i" ; return os; } istream& operator >>(istream& is, Complex& c) { string s; is >> s; int pos = s.find ("+" , 0 ); string stmp = s.substr (0 , pos); c.Re = atof (stmp.c_str ()); stmp = s.substr (pos + 1 , s.length () - pos - 2 ); c.Im = atof (stmp.c_str ()); return is; }
对于 <<
操作符,通常将其 重载为全局函数 ,而不是作为 ostream
类的成员函数。这是因为 <<
操作符的左侧通常是一个 ostream
对象(如 cout
),而右侧是我们想要插入到流中的对象。如果 <<
作为 ostream
的成员函数,那么它将只能访问 ostream
对象的内部状态,而不能直接访问其右侧的对象。对于 >>
同理。
重载类型转换运算符 1 2 3 4 5 6 class Complex { double Re, Im; public : Complex (double r = 0 , double i = 0 ) :Re (r), Im (i) {}; operator double () { return Re; } };
重载自增、自减运算符 前置运算符作为一元运算符重载,即 T& operator++();
(重载为成员函数)或 T1 & operator++(T2);
(重载为全局函数);而后置运算符作为二元运算符重载,多写一个没用的参数,即 T operator++(int);
(重载为成员函数)或 T1 operator++(T2, int);
(重载为全局函数)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Complex { double Re, Im; public : Complex (double r = 0.0 , double i = 0.0 ) : Re (r), Im (i) {} Complex& operator ++() { ++Re; return *this ; } Complex operator --(int ) { Complex temp (*this ) ; --Im; return temp; } friend Complex operator ++(Complex& c, int ); friend Complex& operator --(Complex& c); }; Complex operator ++(Complex& c, int ) { Complex temp (c) ; ++c.Re; return temp; } Complex& operator --(Complex& c) { --c.Im; return c; }
十二、继承和派生 派生类 派生类拥有基类的全部成员函数和成员变量。使用
1 2 3 class 派生类名 : public 基类名 {}
如果多种事物,有一些共同的特点,又有一些各自不同的特点,适合概括所有事物的共同特点,写一个基类。然后为每种事物写一个类,都从基类派生而来。
继承关系 基类 A、B 是基类 A 的派生类,逻辑上要求一个 B 对象也是一个 A 对象。
复合关系 类 B 中有成员变量 k ,k 是类 A 的对象,则 A 和 B 是复合,逻辑上要求 A 对象是 B 对象的固有属性或组成部分。
1 2 3 4 5 6 7 class CPoint { double x,y; }; class CCircle { double r; CPoint center; };
循环定义的写法:
1 2 3 4 5 6 7 class CMaster ; class CDog { CMaster* pm; }; class CMaster { CDog* dogs[10 ]; };
派生类覆盖基类成员 覆盖 派生类可以定义一个和基类成员同名的成员。在派生类中访问这类成员时,缺省的情况是访问派生类中定义的成员。要在派生类中访问由基类定义的同名成员时,要使用作用域符号 ::
。但一般基类和派生类不定义同名成员变量。
派生类的构造函数 在创建派生类的对象时,需要调用基类的构造函数初始化派生类对象中从基类继承的成员。
可在派生类的构造函数中,为基类的构造函数提供参数,或省略基类构造函数以自动调用基类的默认构造函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Bug {private : int nLegs; int nColor; public : int nType; Bug (int legs, int color); void PrintBug () {}; }; class FlyBug : public Bug { int nWings; public : FlyBug (int legs, int color, int wings); }; Bug::Bug (int legs, int color) { nLegs = legs; nColor = color; } FlyBug::FlyBug (int legs, int color, int wings) { nLegs = legs; nColor = color; nType = 1 ; nWings = wings; } FlyBug::FlyBug (int legs, int color, int wings) :Bug (legs, color) { nWings = wings; } int main () { FlyBug fb (2 , 3 , 4 ) ; fb.PrintBug (); fb.nType = 1 ; fb.nLegs = 2 ; return 0 ; }
在创建派生类的对象时,先执行基类的构造函数,用以初始化派生类对象中从基类 继承的成员;再执行成员对象类的构造函数,用以初始化派生类对象中 成员对象;最后执行派生类自己的构造函数。在派生类对象消亡时,先执行派生类自己的析构函数;再依次执行各成员对象类的析构函数;最后执行基类的析构函数。
继承的赋值兼容规则 public 继承:
1 2 3 4 class base {};class derived : public base {};base b; derived d;
派生类的对象可以赋值给基类对象 b = d;
派生类对象可以初始化基类引用 base& br = d;
派生类对象的地址可以赋值给基类指针 base* pb = & d;
protected 继承:基类的 public 成员和 protected 成员成为派生类的 protected 成员。不是 “是” 的关系。
private 继承:基类的 public 成员成为派生类的 private 成员,基类的 protected 成员成为派生类的不可访问成员。不是 “是” 的关系。
基类与派生类的指针强制转换 公有派生的情况下, 派生类对象的指针可以直接赋值给基类指针1 Base* ptrBase = &objDerived;
ptrBase
指向的是一个 Derived
类的对象;*ptrBase
可以看作一个 Base
类的对象,访问它的 public
成员直接通过ptrBase
即可,但不能通过 ptrBase
访问 objDerived
对象中属于 Derived
类而不属于 Base
类的成员;
即便基类指针指向的是一个派生类的对象,也不能通过基类指针访问基类没有,而派生类中有的成员。
通过强制指针类型转换,可以把 ptrBase
转换成 Derived
类的指针1 2 Base* ptrBase = &objDerived; Derived* ptrDerived = (Derived*)ptrBase;
程序员要保证 ptrBase
指向的是一个 Derived
类的对象,否则很容易会出错。
直接基类与间接基类 在声明派生类时,只需要列出它的直接基类。派生类沿着类的层次自动向上继承它的间接基类。
派生类的成员包括:
派生类自己定义的成员;
直接基类中的所有成员;
所有间接基类的全部成员。