C++编程--类与继承(3)
一、类
类的静态变量
1、在类内进行声明
2、在类外进行初始化
3、只有const static int类型才能在类内进行初始化
错误初始化1:
#include <stdio.h> class TestClass1 { public: static int s; }; void main() { TestClass1::s = 1;//error LNK1120: 1 个无法解析的外部命令;不能在此初始化 }
错误初始化2(类内的初始化):
#include <stdio.h> class TestClass1 { public: //错误的类内初始化 static int s = 1;//error C2864: “TestClass1::s”: 只有静态常量整型数据成员才可以在类中初始化 //正确:只有const static int类型才能在类内进行初始化 //static const int s = 1; //错误:必须为整型 //static const float f = 1.0F; }; void main() { printf("static int s = %d\n", TestClass1::s); }
正确的初始化:
#include <stdio.h> class TestClass1 { public: static int s; const static int i; const static int a = 3; //const int b = 4;//错误的初始化,const也不可以 }; int TestClass1::s = 1;//int不可省略 const int TestClass1::i = 2;//const int不可省略 void main() { printf("static int s = %d\n", TestClass1::s); printf("static int i = %d\n", TestClass1::i); printf("static int a = %d\n", TestClass1::a); }
复制构造函数
在C++中已经默认约定了,类对象可以复制。类对象的复制就是其中各个成员的复制。如果某个类所需要的不是这种默认方式,比如类成员里有一个指针,如果你按照默认方式去复制对象,那么就会造成两个对象共用一块内存,对一个对象的操作就会影响另一个对象,这显然不是我们所想的。那么你可以定义一个复制构造函数。如,下面这段代码:
class A { public: A(const A& ){}; //复制构造函数 };
赋值操作符
class A { public: A(const A& ){}; //复制构造函数 A& operator=(const A&){}//赋值函数 };
注意,拷贝构造函数和拷贝赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建并用另一个已经存在的对象来初始化它时调用的,而赋值函数只能把一个对象赋值给另一个已经存在的对象,使得那个已经存在的对象具有和源对象相同的状态。如:
A a; A b; A c = a; //调用的是拷贝构造函数 c = b; //调用的是赋值函数
当然,如果你不希望类被拷贝和赋值,那么你可以把拷贝函数和赋值函数设为private,这样对象就不能被拷贝和赋值了
例子:
1 #include <stdio.h> 2 3 class TestClass2 4 { 5 int a; 6 7 public: 8 TestClass2() 9 { 10 printf("call TestClass2 default construction\n"); 11 a = 1; 12 } 13 14 TestClass2(const TestClass2& right) 15 { 16 printf("call TestClass2 copy construction\n"); 17 a = right.a; 18 } 19 20 TestClass2& operator=(const TestClass2& right) 21 { 22 printf("call TestClass2 operator=\n"); 23 a = right.a; 24 return *this; 25 } 26 }; 27 28 class TestClass3 29 { 30 TestClass2 tc2; 31 32 public: 33 TestClass3(){} 34 TestClass3(const TestClass2& c2)// : tc2(c2)//当有一个构造函数时,默认构造函数如果没有提供,则默认是私有的 35 { 36 tc2 = c2; 37 } 38 }; 39 40 void main() 41 { 42 //TestClass3 tc;//printf("call TestClass2 default construction\n"); 43 printf("TestClass2初始化\n"); 44 TestClass2 tc2; 45 printf("TestClass3初始化\n"); 46 TestClass3 tc3(tc2); 47 }
运行结果:
TestClass2初始化
call TestClass2 default construction
TestClass3初始化
call TestClass2 default construction
call TestClass2 operator=
注意:为什么使用初始化列表效率要更高,即TestClass2(tc2)
注释掉36,将34注释去掉,使用初始化列表,则运行结果:
TestClass2初始化
call TestClass2 default construction
TestClass3初始化
call TestClass2 copy construction
operator+操作符
1 #include <iostream> 2 using namespace std; 3 4 class Sum 5 { 6 public: 7 Sum(int val = 0); 8 Sum& operator+(Sum& other); 9 10 int Get()const 11 { 12 return m_val; 13 } 14 15 private: 16 int m_val; 17 }; 18 19 Sum::Sum(int val) : m_val(val) 20 { 21 } 22 23 Sum& Sum::operator+(Sum& other) 24 { 25 this->m_val += other.m_val; 26 return *this; 27 } 28 29 void main() 30 { 31 Sum a(3); 32 Sum b(4); 33 Sum c(5); 34 Sum d = a + b + c; 35 36 cout << d.Get() << endl; 37 }
operator<<和友元
有的时候,你需要访问一个类的私有成员,那该怎么办呢,要么这个类公开了一个函数接口,要么我们可以使用友元函数,可以直接的访问类的私有成员,以及其它成员。有的时候,我们把一个类作为另一个类的友元时,此时叫这个类为友元类,那么这个友元类就可以访问另一个类里的所有成员
注意:友元的引入,提高了数据的共享性,加强了函数与函数之间,类与类之间的相互联系,大大提高程序的效率,这是友元的优点,但友元也破坏了数据隐藏和数据封装导致程序的可维护性变差,给程序的重用和扩充埋下了深深的隐患,这是友元的缺点.
1 #include <iostream> 2 using namespace std; 3 4 class UipString 5 { 6 friend std::ostream& operator<<(std::ostream&os, const UipString& obj); 7 8 public: 9 UipString(const char *str = "");//构造函数 10 ~UipString();//析构函数 11 UipString(const UipString& other);//拷贝构造函数 12 UipString& operator=(const UipString &other);//赋值构造函数 13 14 private: 15 char *m_data; 16 }; 17 18 UipString::UipString(const char *str) 19 { 20 if (str == NULL) 21 { 22 m_data = new char[1]; 23 *m_data = '\0'; 24 } 25 else 26 { 27 m_data = new char[strlen(str)+1]; 28 strcpy_s(m_data, strlen(str)+1,str); 29 } 30 } 31 32 UipString::~UipString() 33 { 34 if (m_data) 35 { 36 delete [] m_data; 37 m_data = NULL; 38 } 39 } 40 41 UipString::UipString(const UipString &other) 42 { 43 m_data = new char[strlen(other.m_data)+1]; 44 strcpy(m_data, other.m_data); 45 } 46 47 UipString& UipString::operator=(const UipString &other) 48 { 49 //More effective c++中,不能给自己赋值 50 if (this == &other) 51 { 52 return *this; 53 } 54 55 char *pTemp = new char[strlen(other.m_data)+1]; 56 strcpy(pTemp, other.m_data); 57 delete []m_data; 58 m_data = pTemp; 59 } 60 61 ///////////////////////////////重点//////////////////////////////////////// 62 std::ostream& operator<<(std::ostream &os, const UipString &obj) 63 { 64 //因为是友元函数,可以调用obj的私有成员变量 65 os << obj.m_data <<endl; 66 return os; 67 } 68 ///////////////////////////////重点//////////////////////////////////////// 69 70 void main() 71 { 72 UipString A; 73 cout << "A=" << A; 74 UipString B("Hello world"); 75 cout << "B=" << B; 76 UipString C = B; 77 cout << "C=" << C; 78 A =B; 79 cout << "A=" << A << endl; 80 }
二、继承
类的继承访问特性
is-a关系只有public继承才成立。对于private继承还又是另外一回事了。C++中除了有public继承外,还有private继承和protected继承。在private继承中,派生类只能以私有方式继承基类的公有成员和保护成员,因此,基类的公有成员和保护成员在派生类中成为私有成员。另外,基类的私有成员派生类仍不能被问。
如下代码,20行可以访问,21行不可访问。Child: private Base表示,Child从Base继承的所有成员的访问权限都变为pivate,即外部不能访问Child的基类成员,因为他们的权限都是private
1 #include <iostream> 2 using namespace std; 3 4 class Base 5 { 6 public: 7 int public_a; 8 protected: 9 int protected_b; 10 private: 11 int private_c; 12 }; 13 14 class Child: private Base 15 { 16 public: 17 Child() 18 { 19 public_a = 1; 20 protected_b = 2; //此处可以访问基类的protected成员 21 } 22 }; 23 24 void main() 25 { 26 Child child; 27 //int a = child.public_a; //此处不可以访问 28 }
构造与析构
看点:27行代码怎么初始化基类成员
1 #include <iostream> 2 using namespace std; 3 4 class Base 5 { 6 public: 7 Base(int a):a(a) 8 { 9 cout<< "Base constructor\n"; 10 } 11 ~Base() 12 { 13 cout<< "Base destructor\n"; 14 } 15 16 int GetValue() const 17 { 18 return a; 19 } 20 private: 21 int a; 22 }; 23 24 class Derived: public Base 25 { 26 public: 27 Derived(int a, int b): b(b), Base(a)//注意:调用基类构造函数 28 { 29 cout << "Derived constructor\n"; 30 } 31 ~Derived() 32 { 33 cout << "Derived destructor\n"; 34 } 35 36 int Get() 37 { 38 return b + GetValue(); 39 } 40 private: 41 int b; 42 }; 43 44 void main() 45 { 46 Derived b(2, 3); 47 int a = b.Get(); 48 }
运行结果:
Base constructor
Derived constructor
Derived destructorBase destructor
从运行结果我们可以看出,构造函数是先执行基类的构造然后才是派生类的构造;而析构则是相反的顺序。从代码中我们可以看到,如果基类有数据需要初始化,我们是在派生类里把数据传过去供基类初始化。这种方式称为显示初始化。
多重继承
1 #include <iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 int a; 8 }; 9 10 class B: /*virtual*/ public A 11 { 12 public: 13 int b; 14 }; 15 16 class C: /*virtual*/ public A 17 { 18 public: 19 int c; 20 }; 21 22 class D: /*virtual*/ public B, /*virtual*/ public C 23 { 24 public: 25 int d; 26 }; 27 28 void main() 29 { 30 D cd; 31 //cd.a = 100;//error C2385: 对“a”的访问不明确 32 cd.B::a = 1000; 33 cout << "cd.B::a=" << cd.B::a << endl; 34 }
发现31行cd.a = 1000;这句编译不过,为什么呢,因为这里出现了歧义,编译器不知道这个a是从B类继承下来的a,还是从C类继承下来的,当然我们可以在a的前面加上::作用域限定符,指定是哪个类的a,如cd.B::a = 1000;这种作用域限定符有它的特点:
1)浪费了存储空间,保存了多个相同的副本
2)在访问基类的成员时,要求指明访问路径。
解决方法是使用虚基类,将10,16,22行“virtual”注释去掉。
采用虚基类方式定义派生类,在创建派生类的对象时,类层次结构中虚基类的成员只出现一次,即基类的一个副本被所有派生类对象所共享。
使用虚基类派生方式你可以节约内存空间。再就是避免在多重派生类中类成员的不明确性。
虚函数
如果想通过基类指针调用派生类中覆盖的成员函数,那么只有使用虚函数。
1 #include <iostream> 2 using namespace std; 3 4 class Base 5 { 6 public: 7 Base() 8 { 9 cout << "Base constructor\n"; 10 } 11 12 /*virtual*/ ~Base() 13 { 14 cout << "Base destructor\n"; 15 } 16 17 virtual void Display() const 18 { 19 cout <<"Base::display()\n"; 20 } 21 }; 22 23 class Derived : public Base 24 { 25 public: 26 Derived() 27 { 28 cout << "Derived constructor\n"; 29 } 30 31 /*virtual*/ ~Derived() 32 { 33 cout << "Derived destructor\n"; 34 } 35 36 virtual void Display() const 37 { 38 cout <<"Derived::display()\n"; 39 } 40 }; 41 42 int main() 43 { 44 Base* pb = new Derived(); 45 pb->Display(); 46 if (pb) 47 { 48 delete pb; 49 pb = NULL; 50 } 51 return 0; 52 }
利用虚函数可以在基类和派生类中使用相同的函数名和参数类型,但定义不同的操作。这样,就为同一个类体中所有派生类的同一类行为提供了一个统一的接口。
虚析构函数
如上例子运行结果:
Base constructor
Derived constructor
Derived::display()
Base destructor
将12、31行的“virtual”关键字注释去掉,
运行结果:
Base constructor
Derived constructor
Derived::display()
Derived destructor
Base destructor