C++:类和对象
1.基本概念
类是具有相同属性和行为的对象的抽象集合,是对象的模板,对象是类的实例化。
类包含成员函数(行为)和数据(属性)两个部分,一般成员函数作为接口,而数据作为某些成员函数的操作对象,通常是对外不可见的(私有的)。
类通过对数据的封装、隐藏,增强了安全性并简化了编程,模块化程度相对应C来说更高。
2.基本形式
class name //name是类名 { public: /* 公有数据成员和公有成员函数 对类的对象和它派生的子类是可见的 */ int publicData; //数据成员 void publicFun1(); //成员函数 void publicFun2(){cout<<"publicFun2\n";} //在类的内部进行成员函数定义 private: /* 私有数据成员和私有成员函数 对类的对象和它派生的子类是不可见的,但是对友元函数和友元类是可见的 */ int privateData; void privateFun1(); void privateFun2(){cout<<"privateFun2\n";} protected: /* 保护数据成员和保护成员函数 对类的对象不可见,对它派生的子类是可见的,对友元函数和友元类是可见的 */ int protectedData; void protectedFun1(); void protectedFun2(){cout<<"protectedFun2\n";} } void name::publicFun1() //在类的外部进行成员函数定义 { cout<<"publicFun1\n";} }
3.构造函数和析构函数
当建立一个对象时,它的数据成员是不确定的,类中提供了一种初始化的方法,就是构造函数。
构造函数是没有返回值的,并且函数名和类名一致,允许重载、带默认值。
构造函数需要public,但不能像其他成员函数一样显式调用,它在定义对象的同时被系统自动调用。
若没有定义构造函数,则系统自动生成一个不带参数的默认构造函数。
-----------------------------------------------------------------------------------
析构函数和构造函数相反,在释放对象前自动做一些清理工作。
析构函数名和类名相同,不过要在函数前加一个波浪号(~),并且析构函数不能带参数。
析构函数需要声明在public中,可以显式调用,也可以系统自动调用,当对象的生命周期结束时,系统会自动析构。
例子:
//示例1 #include <iostream> using namespace std; class A { private: int a; int b; static int cnt; // 静态数据成员 int cnt_; public: A(){ /*默认构造函数*/ a = 1; b = 1; cnt_=cnt++; cout << "here is A() " << a << "," << b << endl; } A(int ta){ /*重载了A()*/ A::a = ta; A::b = 1; cnt_ = cnt++; cout << "here is A(int ta) " << a <<","<< b << endl; } A(int ta, int tb) :a(ta), b(tb){ /*使用参数列表赋值*/ cnt_ = cnt++; cout << "here is A(int a,int b) " << a << "," << b << endl; } ~A(){ cout << "here is ~A() "<< cnt_ << endl; } /*析构函数*/ }; int A::cnt = 0; // 静态数据成员要在类外定义 int main() { A a; //使用默认构造函数 A b(2); //显式调用构造函数 A(int ta) A c(1, 2); //显式调用构造函数 A(int ta,int tb) return 0; }
输出:
here is A() 1,1 here is A(int ta) 2,1 here is A(int a,int b) 1,2 here is ~A() 2 here is ~A() 1 here is ~A() 0
注意析构函数的调用顺序。
4.静态成员
(1).静态数据成员
静态数据成员在内存中只有一个拷贝,是所有对象共享的,而其他的数据成员在每个对象中都有一个拷贝。
静态数据成员需要在类外定义。
示例如上面的示例1,在每个对象的析构函数中打印静态数据成员cnt,可以发现cnt是不同的。
(2).静态成员函数
静态成员函数只可以使用类的静态成员(数据和函数),若要使用非静态成员,需要通过对象来引用。
//示例2 #include <iostream> using namespace std; class A { private: int a; static int b; public: A(){ a = 1; b = 2; } void fun1(){ cout << a << "," << b << endl; } //static void fun2(){ cout << a << "," << b << endl; } // Error:非静态成员引用必须与特定对象相对 static void fun2(){ cout << b << endl; } //只可以使用静态数据成员 static void fun3(A ta){ cout << ta.a << "," << b << endl; } // 通过对象引用非静态数据成员 }; int A::b = 0; int main() { A a; A t; a.fun1(); a.fun2(); a.fun3(t); return 0; }
5.初始化参数列表
使用初始化参数列表的格式如下:
className(int a,int b):data1(a),data2(b){} //data1,data2是数据成员
使用初始化列表的好处是提高性能,相当于初始化和赋值的区别。另外对于以下情况必须使用初始化列表:
· 常量成员,常量不能赋值,只能初始化
· 引用类型,引用必须在定义的时候初始化,不能重新赋值
· 没有默认构造函数的类型,初始化列表不用调用默认构造函数而直接调用拷贝构造函数
另外,成员的初始化顺序是按照类中出现的顺序而不是按照初始化列表的顺序,例如:
struct example // 一个错误的例子 { int a; // a首先出现 int b; example(int x):b(x),a(b){} //虽然b在a前,但是应该先初始化a再初始化b,所以这里会报错 }; // 应该养成安装成员出现顺序写初始化列表的习惯