(一)类
-
类的一般语法形式
struct/class 类名:继承方式 基类,...{ 访问控制限定符: 类名(形参表):初始化列表{...}//构造函数 ~类名(void){...}//析构函数 返回类型 函数名(形参表){...}//成员函数 数据类型 变量名;//成员变量 };
-
访问控制限定符
1)public:公有成员,任何位置都可以使用
2)private:私有成员,只有类自己的成员函数才能使用
3)protected:保护成员(后面讲) -
构造函数(constructor)
1)语法class 类名{ 类名(形参表){ //主要负责初始化对象(初始化成员变量) } };
2)函数名和类名相同,没有返回类型
3)构造函数在创建对象时自动被调用,不能像普通的成员函数一样显式的调用。
注:在每个对象的生命周期,构造函数一定会被调用,但只会被调用一次 -
对象的创建和销毁
1)在栈区创建单个对象
类名 对象(构造实参表);//直接初始化
类名 对象=类名(构造实参表);//拷贝初始化,一般和上面等价
浅析:如果关闭编译器对构造方法的优化,g++ 编译时加上选项-fno-elide-constructors
,A a = A(1);
首相调用单参构造方法A(int i)创建一个新的临时对象比如它叫tmp,然后再用这个对象作为拷贝构造的参数,拷贝构造:A (const A& a)
;实际调用时:A(tmp);
,然后将拷贝构造所返回的对象,作为最终创建出来的对象。即,在未优化的过程中,执行一次A a = A(1)
实际上调用了两个构造方法,创建了两个对象,不过临时对象用完后会被销毁。该结论已经过测试#include <iostream> #include <cstring> using namespace std; class String{ public: String(const char *str = NULL){ cout << "调用 String(const char *)" << endl; cout << "创建了临时对象" << (void *)this << endl; m_str = new char[strlen(str ? str : "")+1]; strcpy(m_str, str); } String(const String& that){ cout << "调用 copy constructor" << endl; m_str = new char[strlen(that.m_str) + 1]; strcpy(m_str, that.m_str); cout << "我要创建的对象已经创建完成,临时对象没什么用了" << endl; } ~String(void){ cout << "析构函数析构了" << (void *)this << endl; delete[] m_str; } private: char *m_str; }; int main(void){ String s = "hello"; cout << "我正在使用" << (void *)&s << ",它不会被析构" << endl; cout << "程序即将结束了," << (void *)&s << "不用了,即将被析构" << endl; return 0; }
执行如下:
2)在栈区创建多个对象(对象数组)
类名 对象数组[元素个数] = {类名(构造实参表),…};3)在堆区创建/销毁单个对象//重点掌握
创建:
类名* 对象指针 = new 类名(构造实参表);
注:new操作符在分配完内存后,将会自动调用构造函数完成对象的创建;而如果malloc只能分配内存,不会调用构造函数,不能完成对象的创建.
销毁:
delete 对象指针;4)在堆区创建/销毁多个对象(对象数组)
创建:
类名* 对象指针=new 类名[元素个数]{类名(构造实参表),…}
销毁:
delete[] 对象指针; -
多文件编程//参考04project
1)类的声明放在".h"头文件中
2)类的实现放在".cpp"源文件中
(二) 构造函数和初始化列表
-
构造函数可以重载、也可以带有缺省参数,如string类的构造方法
(1)string (void);
(2)string (const string& str);
(3)string (const string& str, size_t pos, size_t len = npos);
(4)string (const char* s);
(5)string (const char* s, size_t n);
(6)string (size_t n, char c);#include <iostream> using namespace std; class Student{ public: Student(const string& name,int age,int no){ cout << "构造函数" << endl; m_name = name; m_age = age; m_no = no; } void who(void){ cout << "我叫" << m_name << ",今年" << m_age << "岁,学号是" << m_no << endl; } private: string m_name; int m_age; int m_no; }; int main(void){ //在栈区创建单个对象 //Student s("张飞",28,10011); Student s = Student("张飞",28,10011); s.who(); //在栈区创建多个对象 Student sarr[3] = { Student("貂蝉",24,10012), Student("小乔",23,10013), Student("孙尚香",26,10014)}; sarr[0].who(); sarr[1].who(); sarr[2].who(); //在堆区创建单个对象 Student* ps = new Student("林黛玉",20,10015); ps->who();//(*ps).who() delete ps; ps = NULL; //在堆区创建多个对象 Student* parr = new Student[3]{ Student("潘金莲",28,10016), Student("孙二娘",27,10017), Student("白骨精",30,10018)}; parr[0].who();//(parr+0)->who() parr[1].who();//(parr+1)->who() parr[2].who();//(parr+2)->who() delete[] parr; parr = NULL; return 0; }
-
缺省构造函数(无参构造函数)
1)如果类中自己没有定义构造函数,那么编译器会为该类提供一个缺省的无参构造函数:
–》对于基本类型的成员变量,不做初始化
–》对于类 类型的成员变量(成员子对象),会自动调用相应类的无参构造函数来初始化
2)如果类中自己定义了构造,无论是否有参数,编译器都不会再提供缺省无参构造函数.#include <iostream> using namespace std; class A{ public: A(void){ cout << "A的无参构造函数" << endl; m_i = 0; } int m_i; }; class B{ public: B(void){ m_j = 100; } int m_j;//基本类型的成员变量 A m_a;//类类型的成员变量(成员子对象) }; int main(void){ B b;//匹配B类的缺省无参构造函数 cout << b.m_j << endl;//100 cout << b.m_a.m_i << endl;//0 return 0; }
在这个B b的过程中,首先分配内存,然后构造B类中的成员子对象(即自定义的类的对象),构造顺序按照声明顺序,依次调用缺省的构造函数(如果不想使用缺省的构造函数,需要使用初始化列表),然后执行B类的构造方法,来初始化B类对象b的其他成员(除了成员子对象以外的成员) -
类型转换构造函数(单参构造函数)
class 类名{
[explicit] 类名(源类型){}
};
可以实现源类型变量到当前类类型对象的隐式转换功能,如:类名 对象 = 源类型, string s = “123”;在这个过程实例化对象s的过程中,首先调用上面的重载构造方法(4), 即"123"为const char *,生成一个string对象,然后再将这个生成的string对象完全拷贝给s,显示地写为:string s = string(“123”);也可以直接初始化要创建的对象string s(“123”);
注:可以适用explicit关键字修饰该构造函数,强制要求类型转换的过程必须显式完成.#include <iostream> using namespace std; class Integer{ public: Integer(void){ cout << "Integer的无参构造" << endl; m_i = 0; } //int->Integer:类型转换构造函数 /*explicit*/ Integer(int i){ cout << "Integer的有参构造" << endl; m_i = i; } void print(void){ cout << m_i << endl; } private: int m_i; }; int main(void){ Integer i; i.print();//0 //1)先将100作为构造实参,构造一个Integer临 //时对象; //2)再使用临时对象对i进行赋值 i = 100;//隐式转换 i.print();//100 //上面隐式转换代码可读性差,推荐使用显式 //i = (Integer)200;//C风格强制转换 i = Integer(200);//C++风格强制转换 i.print();//200 return 0; }
-
拷贝(复制)构造函数
1)用一个已存在的对象作为同类型对象的构造实参,创建新的副本对象,会调用该类的拷贝构造函数。class 类名{ 类名(const 类名& ){ ... } }; A a1(...); A a2(a1);//匹配A类的拷贝构造函数 A a2 = a1;//和上面完全等价
2)如果一个类没有显式定义拷贝构造函数,那么编译器会为该类提供一个缺省的拷贝构造函数:
–》对于基本类型的成员变量,按字节复制
–》对于类类型的成员变量(成员子对象),自动调用相应类拷贝构造函数,完成拷贝初始化.注:在实际开发中,一般不需要自己定义拷贝构造函数,因为编译器缺省提供的已经很好用了。缺省拷贝为浅拷贝,当成员中有指针时,需要使用自定义深拷贝。
#include <iostream> using namespace std; class A{ public: A(int data = 0){ cout << "A(int=0)" << endl; m_data = data; } //拷贝构造函数 A(const A& that){ cout << "A(const A&)" << endl; m_data = that.m_data; } int m_data; }; int main(void){ const A a1(100); cout << a1.m_data << endl;//100 A a2(a1);//拷贝构造 //A a2 = a1;//和上面等价 cout << a2.m_data << endl;//100 return 0; }
3)拷贝构造函数调用时机
–》用已定义的对象作为同类型对象的构造实参
–》以对象形式向函数传递参数
–》从函数中返回对象(有可能被编译器优化掉)#include <iostream> using namespace std; class A{ public: A(void){ cout << "A(void)" << endl; } A(const A& that){ cout << "A(const A&)" << endl; } }; void func1(A a){} A func2(void){ A a;//无参 cout << "&a:" << &a << endl; return a;//拷贝 } int main(void){ A a1;//无参 A a2 = a1;//拷贝 func1(a1);//拷贝 /* 正常情况func2返回a拷贝到临时对象,临时对象 * 再拷贝给a3,发生两次拷贝;但是因为编译器优 * 化让a3直接引用a,不再发生拷贝*/ //去优化编译选项: //g++ 09cpCons.cpp -fno-elide-constructors A a3 = func2();//拷贝 cout << "&a3:" << &a3 << endl; return 0; }
-
初始化列表
1)语法class 类名{ 类名(形参表):成员变量1(初值),成员变量2(初值),... { ... ... } }; class Student{ //先定义成员变量,再赋初值 public: Student(const string& name,int age,int no){ m_name = name; m_age = age; m_no = no; } //定义成员变量同时初始化 Student(const string& name,int age,int no) :m_name(name),m_age(age),m_no(no){} string name; int age; int no; };
注 !!!!C++初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序,所以先声明的成员的初始化赋值一定不要依赖后边声明的成员,要把被依赖的放到前边。或者想办法消除依赖关系
2)大多数情况下,使用初始化列表和原来在构造函数中赋初值结果相同,两者可以任选,但是有些特殊场景,必须要使用初始化列表:
–》如果有类类型的成员变量(成员子对象),并希望以有参方式对其初始化,则必须使用初始化列表指明需要的构造实参.
–》子类中构造方法,希望以有参的方式对基类进行初始化
–》如果有引用型或const修饰的成员变量,必须使用初始化列表来显式的初始化。#include <iostream> using namespace std; int num = 200; class A{ public: /*A(void){ ci = 100; ri = num; }*/ A(void):ci(100),ri(num){} const int ci; int& ri; }; int main(void){ A a; cout << a.ci << "," << a.ri << endl; return 0; }
#include <iostream> using namespace std; class A{ public: A(int data){ cout << "A(int)" << endl; m_data = data; } int m_data; }; class B{ public: //m_a(123):显式指明成员子对象需要的构造实参 B(void):m_a(123){ cout << "B(void)" << endl; } A m_a;//成员子对象 }; int main(void){ B b; cout << b.m_a.m_data << endl;//123 return 0; }