什么是类
类 是 面向对象的基础。c里面是没有对象的,只有数据,即静态的死物。
从面向过程升级到面向对象后,有了对象的概念,对象是数据与方法的合体,是动态的活物。
类代表着一类事物的特征。而对象,是类的具体化,实例化。
类的声明与定义
一般来说,类的声明在相应的头文件中,类的定义在相应的源文件中。这样实现了接口与实现的分离。
//Dog.h #ifndef _DOG_H_ #define _DOG_H_ #include <string> #include <iostream> class Dog { //类的声明 public: //公共成员 Dog(){} //默认构造函数 Dog(float weight) : weight(weight){ //可以隐式转换 sex = true; } explicit Dog(bool isMale) : sex(isMale){} //explicit 修饰不可以隐式转换 Dog(float weight , bool sex); Dog(const Dog &dog) : weight(dog.weight) , sex(dog.sex) , name(dog.name) , power(dog.power){}; //复制构造函数 Dog& operator=(const Dog &dog){ //赋值函数 weight = dog.weight; sex = dog.sex; } ~Dog(){ //析构函数 } void bellow(); inline std::string toString(); // 类定义外部的inline方法,方法定义必须与类定义在同一个文件 float getWeight() const{ //类定义内部定义的方法,默认为inline return weight; } void info(std::ostream& out); static int getNum() { //静态成员函数 return num; } // int t1 = 1; //error , 类定义中成员变量不能初始化。 const static int t2 = 3; //只有 const static 类型才能在类定义中初始化。 static int t3; //static 成员变量初始化写在源文件中。 protected: //保护成员 private: //私有成员 float weight; bool sex; mutable int power; //mutable类型变量,在const 对象中依然可以修改。 std::string name; static int num; // 静态成员变量 }; std::string Dog::toString(){ // 类定义外部的inline方法,方法定义必须与类定义在同一个文件 std::string str("Dog[name="); str+=name; str+=",weight="; str+=weight; str+=",sex="; str+=sex; str+="]"; return str; } #endif
//Dog.cpp #include "Dog.h" #include <iostream> void Dog::bellow(){ //类方法的定义 std::cout<<"wang wang wang"<<std::endl; } void Dog::info(std::ostream& out){ out << "Dog[name=" << name <<",weight=" << weight <<",sex=" << sex <<"]" << std::endl; } //const int Dog::t2; //const static 成员定义。 int Dog::t3 = 10; //static 成员初始化。
类的实例化
Dog d(1.0f);
Dog *d2 = new Dog(1.0f);
类的初始化:构造函数
当创建一个类对象时,构造函数将会被调用。
如:
语句 Dog d(1.0f);
Dog *d2 = new Dog(1.0f);
会使编译器调用 Dog(float w) : weight(w) {} 来初始化对象
构造函数分为3部分:函数签名(函数名加形参列表),即 Dog(float w)
初始化列表 冒号与大括号之间的部分为初始化列表。即 weight(w)
构造函数 即大括号中的内容。
构造函数执行时,先执行初始化列表,然后再执行构造函数。
先执行初始化列表:
初始化列表的任务是 初始化类中的成员变量。顺序是按照类中声明的顺序挨个初始化。
类类型成员初一定初始化,在初始化列表的,就用初始化列表中的初始化方法。不在初始化列表的,就调用类的默认构造函数。 从这里可以看出,没有默认构造函数的类类型,引用类型,const类型 必须在初始化列表。
内置类型和复合类型的不一定初始化,如果在初始化列表,就用初始化列表中的初始化方法。如果不在,就得看对象的位置,全局的初始化,局部的不初始化。
然后再执行构造函数
//playdog.cpp #include <iostream> #include <cstdlib> #include "Dog.h" Dog gloabDog; int main(){ Dog localDog; gloabDog.info(std::cout); localDog.info(std::cout); return EXIT_SUCCESS; }
输出:
由此,可以看出对象是局部变量的情况下,没有出现在相应初始化列表的内置类型类成员没有初始化。
复制构造函数与赋值函数
复制构造函数是一个特殊的构造函数。它只有一个参数,相同类对象的引用。
赋值函数是将相同类的对象赋值给对象。
Dog(const Dog &dog) : weight(dog.weight) , sex(dog.sex) , name(dog.name) , power(dog.power){}; //复制构造函数 Dog& operator=(const Dog &dog){ //赋值函数 weight = dog.weight; sex = dog.sex; }
Dog d1; Dog d2(d1); //调用复制构造函数 Dog d3 = d1; //调用复制构造函数 Dog d4; d4 = d1; //调用赋值函数
赋值构造函数,赋值函数,如果不写的话,编译器会默认一个。系统默认的 就是将成员变量按照声明的顺序挨个复制一遍。
复制构造函数出现的地方:
1,上面的显示调用。
2,对象作为方法参数传入, 对象作为方法返回值返回。
3,初始化容器
4,初始化数组元素
Dog d2(d1); //显示调用 std::string caculate(std::string str1 , std::string str2); //形参,返回值 std::vector<std::string> svec(5); //先调用string的默认构造函数生成一个string临时对象,然后调用5次复制构造函数来初始化容器 std::string[] strs = { //显示调用构造函数生成临时对象,然后调用复制构造函数来初始化数组元素 string("hello"), string("word") }
析构函数
析构函数是删除对象时调用的方法,用来释放对象占用的资源。析构函数是无论是否编写,编译器都会默认一个析构函数。这个默认的析构函数就是按照成员变量声明的顺序的倒序挨个释放资源,如果是类类型,就调用该对象的析构函数。
调用析构函数的时候,先调用用户写的,然后再调用编译器默认的。
三法则
复制构造函数,赋值函数,析构函数, 这三个函数什么时候需要写呢?
一般来说,类中有指针成员变量,或者有需要特殊控制的资源,就需要写。这是3个函数一定是同时需要写,或同时不需要写。静态成员
类中static 修饰静态成员。静态成员属于类,不属于任何一个对象。
所以 static方法中不能使用 this,不能访问非静态方法,非静态变量。
静态成员变量在类定义时初始化。定义在类定义中,初始化在类方法实现源文件中。
c++ primer 中说 const static 成员在类定义中声明初始化,但是还必须在类实现源文件中在空初始化一次。
如: 类定义中 const static int t2 = 3; 但是还必须在Dog.cpp中 const int t2;
但在我的机器上,Dog.cpp中不加这一句仍然没问题。
static 成员的调用
int main(){ std::cout << Dog::t3 << std::endl; std::cout << Dog::t2 << std::endl; return EXIT_SUCCESS; }
类的封装
这里由类的成员访问限定符(member access specifier)实现。
public 任意都可访问。
private 只有本类可访问
protected 不能被类外访问(这点与私有成员类似),但可以被派生类的成员函数访问。
默认内联函数
c++中,inline表示内联函数。在程序调用这些成员函数时,并不是真正地执行函数的调用过程(如保留返回地址等处理),而是把函数代码嵌入程序的调用点。这样可以大大减少调用成员函数的时间开销。
C++要求对一般的内联函数要用关键字inline声明,但对类内定义的成员函数,可以省略inline,因为这些类内定义成员函数已被隐含地指定为内联函数。
如 float getWeight() const 就默认为内联函数了。
不在类定义内定义的内联函数,必须在类定义同一个文件中定义。
如 inline std::string toString(); 必须也在Dog.h 中定义,如果在Dog.cpp中定义,就会出现编译错误。
构造函数隐式转换
只有一个形参的构造函数可以参与隐式转换。
如 因为有构造函数 Dog(float weight)
Dog d = 1.0f;
编译器会把 float 类型 的 1.0f 通过 Dog(float weight) 隐式转换成一个临时的 Dog 类型对象,然后赋值给d.
explicit 修饰构造函数 可以禁止 隐式转换。