C++类和对象
封装
类的封装性
封装:
- 把变量和函数合成一个整体
- 对变量和函数进行访问控制
- 类内部没有访问权限之分,所有成员可以相互访问
- 在类的外部,访问权限才有意义。public protected private
- 在累的外部值public修饰的成员才能被访问,在没有涉及继承与派生时,privateg和protected是同等级的,外部不允许访问。
类的初识
class 类名 // 私有,抽象概念, 系统不会给其分配空间 { public: // 公有 类的外部可访问 protected: // 保护 类的外部不可访问 private: // 私有 类的外部不可访问 };
#include <iostream> #include <string> #include <iostream> using namespace std; class Person { public: void say() { money = 100; cout << "我是傻逼" << endl; } protected: int age; private: int money; }; int main(int argc, char* argv[]) { Person p; // 虽然不可访问私有数据,但是可以使用公有方法访问私有变量 p.say(); return 0; }
- stuct和class的区别是struct的默认权限是公有的,而class默认权限是私有的。
将成员变量设为私有,可以赋予客户端访问数据的一致性。如果成员变量不是public,客户端唯一能够访问对象的方法就是通过成员函数。如果类中所有public权限的成员都是函数,客户在访问类成员的时候只会访问默认函数,不需要考虑访问成员需不需要添加()
可以细微划分访问控制,使用成员函数可以使得我们对变量的控制处理更加精细。如果我们让所有成员变量为public,每个人都可以读写它。如果我们设置为private,我们可以实现不准访问,只读访问,读写访问,设置可以写出只写访问。
构造和析构
构造函数和析构函数,这两个函数会被编译器自动调用,构造函数完成对象的初始化动作,析构函数在对象结束的时候完成清理工作。
如果你不提供构造函数和析构函数,编译器会给你增加默认的操作,但是默认操作不会做任何操作。
构造函数是创建对象时为成员属性赋值,构造函数由编译器自动调用,无需手动调用。析构函数主要用于对象销毁的时候自动调用,执行一些清理操作
构造和析构函数定义
构造函数名和类名相同,没有返回值,但是可以有参数 ClassName(){}
析构函数在类名前加~,没有返回值,不能重载,不能有参数 ~ClassName(){}
#include <iostream> #include <string> #include <iostream> using namespace std; class Person { public: Person() { cout << "无参构造函数" << endl; } Person(int num) { this->num = num; cout << "有参数构造函数" << endl; } Person(const Person &other) { cout << "拷贝构造函数" << endl; } ~Person() { cout << "析构函数" << endl; } private: int num; }; int main(int argc, char* argv[]) { Person p; Person p1(1); return 0; }
构造的分类以及调用
- 构造函数的分类
按照参数类型:无参构造和有参构造
按照类型分类:普通构造和拷贝构造
- 构造函数的调用
无参构造的调用形式:
Person p; // 隐式调用 Person p1 = Person(); // 显示调用 Person(); // 匿名对象调用
有参构造的调用形式:
Person p(1); // 隐式调用 Person p1 = Person(1); // 显示调用 Person p2 = 20; // 隐式转换调用(只针对于有一个参数),尽量别用该方式 Person(1); // 匿名对象调用
拷贝构造的调用形式:默认拷贝构造是浅拷贝,就对象初始化新对象的时候调用拷贝构造函数。
Person p; Person p2 = Person(p); // 显示调用 Person p3(p); // 隐式调用 Person p4 = p; // 使用等号隐式转换
注意: 构造函数和析构函数的顺序相反。
下方不会调用拷贝构造函数:
Person p(10); Person p2; p2 = p;
对于任何一个类,C++编译器至少会给我们写的类增加3个函数:
- 默认构造
- 默认析构
- 默认拷贝构造
对类中的非静态成员属性简单的值拷贝,如果用户定义了拷贝构造,则C++不会再提供任何默认构造函数,如果用户提供了默认构造函数,C++不会提供默认无参构造函数,但是会提供拷贝构造函数。
因此我们在设计类的时候一般需要实现无参构造,有参构造,拷贝构造和析构函数。
深拷贝与浅拷贝
同一个对象之间可以赋值,使得2个对象的成员变量的值相同,两个对象仍然是独立的两个对象,这种情况被称为浅拷贝。一般情况下,浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间的时候,析构函数做了动态内存释放的处理会导致内存重复释放的问题。
浅拷贝,程序崩溃 #include <iostream> #include <string> #include <iostream> using namespace std; class Person { public: Person() { name = nullptr; num = 0; cout << "无参构造" << endl; } Person(char *name, int num) { this->name = (char *) calloc(1, strlen(name) + 1); if(this->name == nullptr) { cout << "构造失败" << endl; } strcpy(this->name, name); this->num = num; cout << "有参构造" << endl; } ~Person() { if(name != nullptr) { cout << "空间被释放" << endl; free(name); name = nullptr; } cout << "析构函数" << endl; } void show() { cout << "num:" << num << " name:" << name << endl; } private: char *name; int num; }; int main(int argc, char* argv[]) { Person p("aasda", 10); p.show(); Person p2 = p; return 0; }
深拷贝,正常释放 #include <iostream> #include <string> #include <iostream> using namespace std; class Person { public: Person() { name = nullptr; num = 0; cout << "无参构造" << endl; } Person(char *name, int num) { this->name = (char *) calloc(1, strlen(name) + 1); if(this->name == nullptr) { cout << "构造失败" << endl; } strcpy(this->name, name); this->num = num; cout << "有参构造" << endl; } Person(const Person &other) { this->name = (char *) calloc(1, strlen(other.name) + 1); strcpy(this->name, other.name); this->num = other.num; } ~Person() { if(name != nullptr) { cout << "空间被释放" << endl; free(name); name = nullptr; } cout << "析构函数" << endl; } void show() { cout << "num:" << num << " name:" << name << endl; } private: char *name; int num; }; int main(int argc, char* argv[]) { Person p("aasda", 10); p.show(); Person p2 = p; p2.show(); return 0; }
初始化列表与成员对象
构造函数和其他函数不同,除了有名字,参数列表函数体之外,还有初始化列表。
#include <iostream> #include <string> #include <iostream> using namespace std; class Person { public: Person() : a(0), b(1), c(2) { cout << "无参构造" << endl; } Person(int a, int b, int c) : a(a), b(b), c(b) { cout << "无参构造" << endl; } void show() { cout << "a:" << a << " b:" << b << " c:" << c << endl; } private: int a; int b; int c; }; int main(int argc, char* argv[]) { Person p; p.show(); Person p2(8, 9, 10); p2.show(); return 0; }
注意: 初始化列表只能在构造函数中使用
对象成员的初始化列表
在类中定义数据成员一般都是基本数据类型。但是当类中的成员也可以是对象,叫做对象成员。C++中对对象成员的初始化是非常重要的操作,当创建了一个对象的时候,C++编译器必须确保调用了所有子对象的构造函数。如果所有子对象有默认构造函数,编译器可以自动调用他们。但是如果子对象没有默认构造函数,或者想指定调用某个构造函数怎么办?初始化列表提供了对象成员的构造函数调用方式。
#include <iostream> #include <string> #include <iostream> using namespace std; class Aaa { public: Aaa(int a) : aaa(a) { cout << "有参构造" << endl; } Aaa() { cout << "无参构造" << endl; } int getA(){ return aaa; } private: int aaa; }; class Person { public: Person() : a(Aaa(5)) {} Person(int a) : a(Aaa(a)) {} void show() { cout << "a:" << a.getA() << endl; } private: Aaa a; }; int main(int argc, char* argv[]) { Person p; p.show(); Person p2(8); p2.show(); return 0; }
explicit 关键字
C++提供了关键字explicit,表示禁止通过构造函数进行隐式转换。声明为explicit的构造函数不能在隐式转换中使用。
explicit是针对只有一个参数的构造函数,或者是除了第一个参数其他的都是默认值的多参数的构造函数。
动态对象的创建
在我们创建数组的时候总是需要提前预定数组长度,然后编译器分配预定长度的数组空间,在使用数组时,会有这样的问题,数组也许空间太大,也许空间太小。所以对于数组而言,如果能动态的分配大小空间最好不过了,多以C提供了动态分配内存函数malloc和free,可以在运行时候从堆中分配存储单元。然而这些函数在C++中不能很好的运行,因为他们不能帮我们完成对象的创建。
对象的创建
当创建一个C++对象的时候,会发生两件事:
- 为对象分配内存空间。
- 调用构造函数初始化内存。
如果我们使用C函数的话会有以下问题:
- 程序员必须确定对象长度
- malloc返回的是一个void指针,C++不允许将void指针赋值给其他指针,必须强转
- malloc可能申请失败,必须判断返回值来保证内存分配成功
- 用户在使用对象之前必须对它初始化,构造函数不能显示调用初始化,用户有可能会忘记调用初始化函数
总之,C的动态内存分配函数太复杂,于是C++中出现了new和delete来创建和销毁一个对象。
new operator
C++中解决动态分配方案就是把创建一个对象所需要的操作都结合在一个称为new的运算符里面,当使用new创建一个对象时,它就在堆里面为对象分配内存并调用构造函数完成初始化。
给基本对象申请空间
int main(int argc, char* argv[]) { int *p = new int(100); cout << *p << endl; int *p2 = new int[5]{1,2,3,4,5}; cout << p2[0] << " " << p2[1] << endl; delete p; delete [] p2; return 0; }
给对象申请空间
#include <iostream> #include <string> #include <iostream> using namespace std; class Person { public: Person() : a(5) { cout << "构造函数" << endl; } ~Person() { cout << "析构函数" << endl; } explicit Person(int a) : a(a) { cout << "有参构造函数" << endl; } void show() { cout << "a:" << a << endl; } private: int a; }; int main(int argc, char* argv[]) { // 申请空间,构造函数 Person *p = new Person; // 析构函数,销毁空间 delete p; Person *p2 = new Person[5]; delete [] p2; Person *p3 = new Person[5]{Person(3),Person(2),Person(2),Person(2),Person(2)}; delete [] p3; Person *p4 = new Person; void *p5 = p4; delete p5; // 不会析构 return 0; }
注意: 如果new没有加[] 释放的时候不用加[],如果new加了[]则delete也需要加[]。
delete 无法从void中寻找析构函数,释放的时候注意类型。
静态成员
在类定义中,成员包括成员变量和成员函数,这些成员可以使用static关键字声明为静态的,称为静态成员。不管这个类创建了多少个对象,静态成员只有一份拷贝,这个拷贝属于这个类的对象共享。
静态成员变量
在一个类中,如果将一个变量声明为static,这种成员称为静态成员变量。与一个的数据成员不同,无论建立多少个对象,该变量只有一份静态数据的拷贝。静态成员变量属于某个类所有的对象共享。静态变量是在编译期间就分配空间,对象还没创建的时候就已经分配了空间。
静态成员变量必须在类中声明,在类外定义。静态数据成员不属于某个对象,在为对象分配空间的时候不包括静态数据成员所占的控件。静态数据成员可以通过类名或者对象名来引用。
静态成员函数
针对于无法直接访问的private静态成员,可以使用静态成员函数访问。
#include <iostream> #include <string> #include <iostream> using namespace std; class Person { public: Person() : a(5) { cout << "构造函数" << endl; } ~Person() { cout << "析构函数" << endl; } explicit Person(int a) : a(a) { cout << "有参构造函数" << endl; } void show() { cout << "a:" << a << endl; } static int getB() { return b; } private: int a; static int b; }; int Person::b = 20; int main(int argc, char* argv[]) { cout << sizeof(Person) << endl; cout << Person::getB() << endl; return 0; }
C++类和对象(中):https://developer.aliyun.com/article/1459441