// r:read w:writeclass info { private: string m_name; // rw int m_card = 3586286477362; // r string m_Idol; // w public: // rw void SetName(string name){ m_name = name; } string GetName(){ cout << m_name << '\n' << endl; } // r int GetCard(){ cout << m_card << '\n' <<endl; } // w void SetIdol(string idol){ m_Idol = idol; }};
写入数据前的有效性检查。
如果对输入的数据有要求,可以在写入时进行检查。
1 2 3 4 5 6 7 8 9 10 11 12
class info{ private: int m_age; public: void SetAge(int age){ if (age < 0 || age > 150){ cout << "Invalid input, please retry.\n" << endl; return; // to end the function } m_age = age; }};
To simplify a bit, an aggregate in C++ is either a C-style array (17.7 -- Introduction to C-style arrays), or a class type (struct, class, or union) that has:
struct Employee{ int id {}; int age {}; double wage {};};int main(){ Employee frank = { 1, 32, 60000.0 }; // copy-list initialization using braced list Employee joe { 2, 28, 45000.0 }; // list initialization using braced list (preferred) return 0;}
Not allowing class types with private members to be initialized via aggregate initialization makes sense for a number of reasons:
Aggregate initialization requires knowing about the implementation of the class (since you have to know what the members are, and what order they were defined in), which we’re intentionally trying to avoid when we hide our data members.
If our class had some kind of invariant, we’d be relying on the user to initialize the class in a way that preserves the invariant.
Constructors have no names and cannot be called directly. They are invoked when initialization takes place, and they are selected according to the rules of initialization.
If all of the parameters in a constructor have default arguments, the constructor is a default constructor (because it can be called with no arguments).
public: // Default constructor Box() {} // Initialize a Box with equal dimensions (i.e. a cube) Box(int i) : m_width(i), m_length(i), m_height(i) {}// member init list // Initialize a Box with custom dimensions Box(int width, int length, int height) : m_width(width), m_length(length), m_height(height) {}
A class should only have one default constructor. If more than one default constructor is provided, the compiler will be unable to disambiguate which should be used.
我们知道,在 C++ 中,有两种常用的函数传参方式:按值传递和按引用传递。这一节介绍传引用的构造函数。后者有专有名词,叫作 “拷贝构造函数 (Copy constructor)”。
A copy constructor is a constructor that is used to initialize an object with an existing object of the same type. After the copy constructor executes, the newly created object should be a copy of the object passed in as the initializer.
// Copy constructorFraction(const Fraction& fraction) // Initialize our members using the corresponding member of the parameter : m_numerator{ fraction.m_numerator } , m_denominator{ fraction.m_denominator }{ std::cout << "Copy constructor called\n"; // just to prove it works // do not write it}
实际上就是传入要拷贝类对象的引用,通过引用访问类对象获取数据,然后将数据初始化给新的类对象。
It is a requirement that the parameter of a copy constructor be an lvalue reference or const lvalue reference. Because the copy constructor should not be modifying the parameter, using a const lvalue reference is preferred.
A copy constructor should not do anything other than copy an object. This is because the compiler may optimize the copy constructor out in certain cases. If you are relying on the copy constructor for some behavior other than just copying, that behavior may or may not occur.
If you do not provide a copy constructor for your classes, C++ will create a public implicit copy constructor for you. By default, the implicit copy constructor will do memberwise initialization. This means each member will be initialized using the corresponding member of the class passed in as the initializer. In the example below, fCopy.m_numerator is initialized using f.m_numerator , and fCopy.m_denominator is initialized using f.m_denominator.
浅拷贝 (Shallow copy) 就是简单的赋值拷贝操作。这是 C++ 编译器提供的默认拷贝构造函数所使用的拷贝方式。
Because C++ does not know much about your class, the default copy constructor it provides use a copying method known as a memberwise copy (also known as a shallow copy). This means that C++ copies each member of the class individually.
A deep copy allocates memory for the copy and then copies the actual value, so that the copy lives in distinct memory from the source. This way, the copy and source are distinct and will not affect each other in any way.
#include <iostream>using namespace std;// Box Classclass box {private: int length; int* breadth; int height;public: // Constructor box() { breadth = new int; } // Function to set the dimensions of the Box void set_dimension(int len, int brea, int heig) { length = len; *breadth = brea; height = heig; } // Function to show the dimensions of the Box void show_data() { cout << " Length = " << length << "\n Breadth = " << *breadth << "\n Height = " << height << endl; } // Parameterized Constructors for for implementing deep copy box(box& sample) { length = sample.length; breadth = new int; *breadth = *(sample.breadth); height = sample.height; }};// Driver Codeint main(){ // Object of class first box first; // Set the dimensions first.set_dimension(12, 14, 16); // Display the dimensions first.show_data(); // When the data will be copied // then all the resources will also get allocated to the new object box second = first; // Display the dimensions second.show_data(); return 0;}
Classes in the standard library that deal with dynamic memory, such as std::string, handle all of their memory management. So instead of doing your own memory management, you can just initialize or assign them like normal fundamental variables!
优先使用标准库中的类,而不是自己进行内存管理。比如 C++ 中的std::string。顺带一提,如果使用 C 语言风格处理字符串,那么用户定义的拷贝构造函数可能长这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// first we need to deallocate any value that this string is holding!delete[] m_data;// because m_length is not a pointer, we can shallow copy itm_length = source.m_length;// m_data is a pointer, so we need to deep copy it if it is non-nullif (source.m_data)//check to make sure source even has a string{ // allocate memory for our copy m_data = new char[m_length]; // do the copy for (int i{ 0 }; i < m_length; ++i) m_data[i] = source.m_data[i];}else m_data = nullptr;}
When we create a copy of object by copying data of all member variables as it is, then it is called shallow copy
When we create an object by copying data of another object along with the values of memory resources that reside outside the object, then it is called a deep copy
2.
A shallow copy of an object copies all of the member field values.
Deep copy is performed by implementing our own copy constructor.
3.
In shallow copy, the two objects are not independent
It copies all fields, and makes copies of dynamically allocated memory pointed to by the fields
4.
It also creates a copy of the dynamically allocated objects
If we do not create the deep copy in a rightful way then the copy will point to the original, with disastrous consequences.
// This example won't compile because it is (intentionally) incompleteclass NetworkData{private: std::string m_serverName{}; DataStore m_dataQueue{};public: NetworkData(std::string_view serverName)// Set server : m_serverName { serverName } { } void addData(std::string_view data)// Collect data { m_dataQueue.add(data); } void sendData() { // connect to server // send all data // clear data }};int main(){ NetworkData n("someipAddress"); n.addData("somedata1"); n.addData("somedata2"); n.sendData(); return 0;}
To generalize this issue, classes that use a resource (most often memory, but sometimes files, databases, network connections, etc…) often need to be explicitly sent or closed before the class object using them is destroyed. In other cases, we may want to do some record-keeping prior to the destruction of the object, such as writing information to a log file, or sending a piece of telemetry to a server. The term “clean up” is often used to refer to any set of tasks that a class must perform before an object of the class is destroyed in order to behave as expected.
C++ 中提供了析构函数 (Destructor) 用以解决类对象的清理问题。它的设计目的就是允许类在对象被销毁之前执行任何必要的清理操作。
和构造函数类似,析构函数是一种特殊的成员函数。当非聚合类的对象被销毁时,该函数会被自动调用。
析构函数的声明:
1
~d_func(){function_bodies}
和构造函数一样,析构函数必须与类名相同,前面加一个波浪号 (~)。没有返回值,也不写void。
析构函数严格不接受任何参数,因此也不支持重载。一个类只能有一个析构函数。这和构造函数不一样。
Generally you should not call a destructor explicitly (as it will be called automatically when the object is destroyed), since there are rarely cases where you’d want to clean up an object more than once.
If a non-aggregate class type object has no user-declared destructor, the compiler will generate a destructor with an empty body. This destructor is called an implicit destructor, and it is effectively just a placeholder.
If your class does not need to do any cleanup on destruction, it’s fine to not define a destructor at all, and let the compiler generate an implicit destructor for your class. ——15.4 — Introduction to destructors – Learn C++
// This example won't compile because it is (intentionally) incompleteclass NetworkData{private: std::string m_serverName{}; DataStore m_dataQueue{};public: NetworkData(std::string_view serverName) : m_serverName { serverName } { } ~NetworkData() { sendData(); // make sure all data is sent before object is destroyed } void addData(std::string_view data) { m_dataQueue.add(data); } void sendData() { // connect to server // send all data // clear data }};int main(){ NetworkData n("someipAddress"); n.addData("somedata1"); n.addData("somedata2"); return 0;}
To help prevent such errors, members in the member initializer list should be listed in the order in which they are defined in the class. Some compilers will issue a warning if members are initialized out of order.
It’s also a good idea to avoid initializing members using the value of other members (if possible). That way, even if you do make a mistake in the initialization order, it shouldn’t matter because there are no dependencies between initialization values.
#include <iostream>class Foo{private: int m_x {}; // default member initializer (will be ignored) int m_y { 2 }; // default member initializer (will be used) int m_z; // no initializerpublic: Foo(int x) : m_x { x } // member initializer list { std::cout << "Foo constructed\n"; } void print() const { std::cout << "Foo(" << m_x << ", " << m_y << ", " << m_z << ")\n"; }};int main(){ Foo foo { 6 }; foo.print(); return 0;}
当构造 foo 时,只有 m_x 出现在成员初始化列表中,因此 m_x 首先被初始化为 6 。
m_y 不在成员初始化列表中,但它被默认成员初始化,因此它被初始化为 2 。
m_z 既不在成员初始化列表中,也没有被默认成员初始化,因此它使用默认初始化。
在定义构造函数时,我们也可以在函数体内为成员赋值:
1 2 3 4 5 6 7 8 9 10 11 12
class Foo{private: int m_x { 0 }; int m_y { 1 };public: Foo(int x, int y) { m_x = x; // this is an assignment, not an initialization m_y = y; // this is an assignment, not an initialization }
C++ 类中的成员可以是另一个类的对象,我们称该成员为对象成员 (Members that point to or reference objects),该过程称为类的聚合 (Aggergation)。
如何评估为聚合
To qualify as an aggregation, a whole object and its parts must have the following relationship:
The part (member) is part of the object (class) 该(成员)部分属于类(对象)的一部分
The part (member) can (if desired) belong to more than one object (class) at a time 该(成员)部分可以同时属于几个类(对象)
The part (member) does not have its existence managed by the object (class) 该(成员)部分的存在不由类(对象)管理
The part (member) does not know about the existence of the object (class) 该(成员)部分始终不知道类(对象)的存在
In an aggregation, we also add parts as member variables. However, these member variables are typically either references or pointers that are used to point at objects that have been created outside the scope of the class. (成员)部分可以作为成员变量添加。这些成员变量通常是引用或指针,用于指向在类作用域之外创建的对象。
#include <iostream>#include <string>#include <string_view>class Teacher{private: std::string m_name{};public: Teacher(std::string_view name) : m_name{ name } { } const std::string& getName() const { return m_name; }};class Department{private: const Teacher& m_teacher; // This dept holds only one teacher for simplicity, but it could hold many teacherspublic: Department(const Teacher& teacher) : m_teacher{ teacher } { }};int main(){ // Create a teacher outside the scope of the Department Teacher bob{ "Bob" }; // create a teacher { // Create a department and use the constructor parameter to pass the teacher to it. Department department{ bob }; } // department goes out of scope here and is destroyed // bob still exists here, but the department doesn't std::cout << bob.getName() << " still exists!\n"; return 0;}
首先,bob 独立于 department 创建。bob调用一次构造函数
然后,bob被传递给 department 的拷贝构造函数。department调用一次拷贝构造函数
当 department 被销毁时, m_teacher 引用被销毁。department调用一次析构函数
When a data member is declared as static, only one copy of the data is maintained for all objects of the class. Static data members are not part of objects of a given class type.
Inside a class definition, the keyword static declares members that are not bound to class instances. Static members of a class are not associated with the objects of the class: they are independent variables with static or thread(since C++11) storage duration or regular functions.