本文为 C++ 学习笔记,参考《Sams Teach Yourself C++ in One Hour a Day》第 8 版、《C++ Primer》第 5 版、《代码大全》第 2 版。ios
面向对象编程有四个重要的基础概念:抽象、封装、继承和多态。本文整理 C++ 中类与对象的基础内容,涉及抽象和封装两个概念。《C++基础-继承》一文讲述继承概念。《C++基础-多态》一文讲述多态概念。这些内容是 C++ 中最核心的内容。程序员
抽象编程
抽象是一种忽略个性细节、提取共性特征的过程。当用“房子”指代由玻璃、混凝土、木材组成的建筑物时就是在使用抽象。当把鸟、鱼、老虎等称做“动物”时,也是在使用抽象。ide
基类是一种抽象,可让用户关注派生类的共同特性而忽略各派生类的细节。类也是一种抽象,用户能够关注类的接口自己而忽视类的内部工做方式。函数接口、子系统接口都是抽象,各自位于不一样的抽象层次,不一样的抽象层次关注不一样的内容。函数
抽象能令人以一种简化的观点来考虑复杂的概念,忽略繁琐的细节能大大下降思惟及实现的复杂度。若是咱们在看电视前要去关注塑料分子、琉璃分子、金属原子是如何组成一部电视机的、电与磁的原理是什么、图像是如何产生的,那这个电视不用看了。咱们只是要用一台电视,而不关心它是怎么实现的。同理,软件设计中,若是不使用各类抽象层次,那么这一堆代码将变得没法理解没法维护甚至根本没法设计出来。性能
封装学习
抽象是从一种高层的视角来看待一个对象。而封装则是,除了那个抽象的简化视图外,不能让你看到任何其余细节。简言之,封装就是隐藏实现细节,只让你看到想给你看的。this
在程序设计中,就是把类的成员(属性和行为)进行整合和分类,肯定哪些成员是私有的,哪些成员是公共的,私有成员隐藏,公共成员开放。类的用户(调用者)只能访问类的公共接口。spa
// 类:人类 class Human { pubilc: // 成员方法: void Talk(string textToTalk); // 说话 void IntroduceSelf(); // 自我介绍 private: // 成员属性: string name; // 姓名 string dateOfBirth; // 生日 string placeOfBirth; // 出生地 string gender; // 性别 ... }; // 对象:具体的某我的 Human xiaoMing; Human xiaoFang;
对象是类的实例。语句 Human xiaoMing;
和 int a;
本质上并没有不一样,对象和类的关系,等同于变量和类型的关系。设计
不介意外部知道信息使用 public 关键字限定,须要保密的信息使用 private 关键字限定。
构造函数在建立对象时被调用。执行初始化操做。
构造函数形式以下:
class Human { public: Human(); // 构造函数声明 }; Human::Human() // 构造函数实现(定义) { ... }
可不提供实参调用的构造函数是默认构造函数,包括以下两种:
1) 不带任何函数形参的构造函数是默认构造函数
2) 带有形参但全部形参都提供默认值的构造函数也是默认构造函数,由于这种既能够携带实参调用,也能够不带实参调用
当用户未给出任何构造函数时,编译器会自动生成一个构造函数,叫做合成的默认构造函数,此函数对类的数据成员初始化规则以下:
1) 若数据成员存在类内初始化值,则用这个初始化值来初始化数据成员
2) 不然,执行默认初始化。默认值由数据类型肯定。参"C++ Primer 5th"第 40 页
下面这个类由于没有任何构造函数,因此编译器会生成合成的默认构造函数:
class Human { pubilc: // 成员方法: void Talk(string textToTalk); // 说话 void IntroduceSelf(); // 自我介绍 private: // 成员属性: string name; // 姓名 string dateOfBirth; // 生日 string placeOfBirth; // 出生地 string gender; // 性别 };
函数能够有带默认值的参数,构造函数固然也能够。
class Human { private: string name; int age; public: // overloaded constructor (no default constructor) Human(string humansName, int humansAge = 25) { name = humansName; age = humansAge; ... };
可使用以下形式的实例化
Human adam("Adam"); // adam.age is assigned a default value 25 Human eve("Eve, 18); // eve.age is assigned 18 as specified
初始化列表是一种简写形式,将相关数据成员的初始化列表写在函数名括号后,从而能够省略函数体中的相应数据成员赋值语句。
Human::Human(string humansName, int humansAge) : name(humansName), age(humansAge) { }
上面这种写法和下面这种写法具备一样的效果:
Human::Human(string humansName, int humansAge) { name = humansName; age = humansAge; }
复制一个类的对象时,只复制其指针成员但不复制指针指向的缓冲区,其结果是两个对象指向同一块动态分配的内存。销毁其中一个对象时,delete[] 释放这个内存块,致使另外一个对象存储的指针拷贝无效。这种复制被称为浅复制。
以下为浅复制的一个示例程序:
#include <iostream> #include <string.h> using namespace std; class MyString { private: char* buffer; public: MyString(const char* initString) { buffer = NULL; if(initString != NULL) { buffer = new char [strlen(initString) + 1]; strcpy(buffer, initString); } } ~MyString() { cout << "Invoking destructor, clearing up" << endl; delete [] buffer; } int GetLength() { return strlen(buffer); } const char* GetString() { return buffer; } }; void UseMyString(MyString str) { cout << "String buffer in MyString is " << str.GetLength(); cout << " characters long" << endl; cout << "buffer contains: " << str.GetString() << endl; return; } int main() { MyString sayHello("Hello from String Class"); UseMyString(sayHello); return 0; }
分析一下 UseMyString(sayHello);
这一语句:
复制构造函数是一个重载的构造函数,由编写类的程序员提供。每当对象被复制时,编译器都将调用复制构造函数。
复制构造函数函数语法以下:
class MyString { MyString(const MyString& copySource); // copy constructor }; MyString::MyString(const MyString& copySource) { // Copy constructor implementation code }
复制构造函数接受一个以引用方式传入的当前类的对象做为参数。这个参数是源对象的别名,您使用它来编写自定义的复制代码,确保对全部缓冲区进行深复制。
复制构造函数的参数必须按引用传递,不然复制构造函数将不断调用本身,直到耗尽系统的内存为止。缘由就是每当对象被复制时,编译器都将调用复制构造函数,若是参数不是引用,实参不断复制给形参,将生成不断复制不断调用复制构造函数。
示例程序以下:
#include <iostream> #include <string.h> using namespace std; class MyString { private: char* buffer; public: MyString() {} MyString(const char* initString) // constructor { buffer = NULL; cout << "Default constructor: creating new MyString" << endl; if(initString != NULL) { buffer = new char [strlen(initString) + 1]; strcpy(buffer, initString); cout << "buffer points to: 0x" << hex; cout << (unsigned int*)buffer << endl; } } MyString(const MyString& copySource) // Copy constructor { buffer = NULL; cout << "Copy constructor: copying from MyString" << endl; if(copySource.buffer != NULL) { // allocate own buffer buffer = new char [strlen(copySource.buffer) + 1]; // deep copy from the source into local buffer strcpy(buffer, copySource.buffer); cout << "buffer points to: 0x" << hex; cout << (unsigned int*)buffer << endl; } } // Destructor ~MyString() { cout << "Invoking destructor, clearing up" << endl; delete [] buffer; } int GetLength() { return strlen(buffer); } const char* GetString() { return buffer; } }; void UseMyString(MyString str) { cout << "String buffer in MyString is " << str.GetLength(); cout << " characters long" << endl; cout << "buffer contains: " << str.GetString() << endl; return; } int main() { MyString sayHello("Hello from String Class"); UseMyString(sayHello); return 0; }
再看 UseMyString(sayHello);
这一语句:
一样,若是没有提供复制赋值运算符 operator=,编译器提供的默认复制赋值运算符将致使浅复制。
关于复制构造函数的注意事项以下:
class MyString { // 代码同上一示例程序,此处略 }; MyString Copy(MyString& source) { MyString copyForReturn(source.GetString()); // create copy return copyForReturn; // 1. 将返回值复制给调用者,首次调用复制构造函数 } int main() { MyString sayHello ("Hello World of C++"); MyString sayHelloAgain(Copy(sayHello)); // 2. 将 Copy() 返回值做实参,再次调用复制构造函数 return 0; }
上例中,参考注释,实例化 sayHelloAgain 对象时,复制构造函数被调用了两次。若是对象很大,两次复制形成的性能影响不容忽视。
为避免这种性能瓶颈, C++11 引入了移动构造函数。移动构造函数的语法以下:
// move constructor MyString(MyString&& moveSource) { if(moveSource.buffer != NULL) { buffer = moveSource.buffer; // take ownership i.e. 'move' moveSource.buffer = NULL; // set the move source to NULL } }
有移动构造函数时,编译器将自动使用它来“移动”临时资源,从而避免深复制。增长移动构造函数后,上一示例中,将首先调用移动构造函数,而后调用复制构造函数,复制构造函数只被会调用一次。
析构函数在对象销毁时被调用。执行去初始化操做。
每当对象再也不在做用域内或经过 delete 被删除进而被销毁时,都将调用析构函数。这使得析构函数成为重置变量以及释放动态分配的内存和其余资源的理想场所。
假设要模拟国家政体,一个国家只能一位总统,President 类的对象不容许复制。
要禁止类对象被复制,可将复制构造函数声明为私有的。为禁止赋值,可将赋值运算符声明为私有的。复制构造函数和赋值运算符声明为私有的便可,不须要实现。这样,若是代码中有对对象的复制或赋值,将没法编译经过。形式以下:
class President { private: President(const President&); // private copy constructor President& operator= (const President&); // private copy assignment operator // … other attributes };
前面讨论的 President 不能复制,不能赋值,但存在一个缺陷:没法禁止经过实例化多个对象来建立多名总统:
President One, Two, Three;
要确保一个类不能有多个实例,也就是单例的概念。实现单例,要使用私有构造函数、私有赋值运算符和静态实例成员。
将关键字 static 用于类的数据成员时,该数据成员将在全部实例之间共享。
将关键字 static 用于成员函数(方法)时,该方法将在全部成员之间共享。
将 static 用于函数中声明的局部变量时,该变量的值将在两次调用之间保持不变。
将析构函数声明为私有的。略
略
在类中,关键字 this 包含当前对象的地址,换句话说, 其值为&object。在类成员方法中调用其余成员方法时, 编译器将隐式地传递 this 指针。
调用静态方法时,不会隐式地传递 this 指针,由于静态函数不与类实例相关联,而由全部实例共享。要在静态函数中使用实例变量,应显式地声明一个形参,并将实参设置为 this 指针。
sizeof 用于类时,值为类声明中全部数据属性占用的总内存量,单位为字节。是否考虑对齐,与编译器有关。
结构 struct 与类 class 很是类似,差异在于程序员未指定时,默认的访问限定符(public 和 private)不一样。所以,除非指定了,不然结构中的成员默认为公有的(而类成员默认为私有的);另外,除非指定了,不然结构以公有方式继承基结构(而类为私有继承)。