C++ basics-classes and objects (Part 2)

Preface

For any C++ class, the constructor is an important part of it. We have introduced some basic knowledge of classes in the previous article. In this article, we will continue to understand some other functions of classes. and provide some more in-depth discussion of previously covered topics such as constructors.

1. In-depth structure

1.Initialization list

Sometimes we can ignore the difference in data member initialization and assignment, but not always. If the member is const or a reference, we must initialize it. Similarly, when a member belongs to a certain class type and the class does not define a default constructor, the member must also be initialized.

class STU
{
    
    
public:
	//错误,my_id和rid必须要被初始化
	STU(int i)
	{
    
    
		id = i;//正确
		my_id = i;//错误:不可以给const赋值
		rid = i;//错误:rid没有被初始化
	}
private:
	int id;
	const int my_id;
	int& rid;
};

Insert image description here
The only chance we have to initialize a const or reference type data member is through the constructor initializer! ! !

	STU(int i):id(i),my_id(i),rid(id){
    
    }//显示的初始化引用和const成员

The above is our initialization list.
Initialization list: Begins with a colon, followed by a comma-separated list of data members, each "member variable" followed by an initial value or expression in parentheses.
Notice:

  1. Each member variable can only appear once in the initialization list (initialization can only be initialized once)
  2. Reference member variables, const member variables and custom type members (when the class does not have a default constructor) must be placed in the initialization list for initialization.
  3. Try to use an initialization list for initialization, because no matter whether you use an initialization list or not, custom type member variables must be initialized using an initialization list first.
  4. The order in which member variables are declared in a class is the order in which they are initialized in the initialization list, regardless of their order in the initialization list.
class A
{
    
    
public:
	A(int i):a(i),b(a){
    
    }
	void print()
	{
    
    
		cout << "A:" << a << "  B:" << b << endl;
	}
private:
	int b;
	int a;
};
int main()
{
    
    
	A a(10);
	a.print();
	return 0;
}

What does the result of the above code look like? Is it 10, 10?
Insert image description here
As can be seen from the running results, it is obviously not the 10 or 10 above. Why are there random values?
According to our fourth article above, if we declare b first, the initial list will be assigned in the order of declaration, regardless of the order in the initialization list, so b is assigned first. The value of b comes from a. At this time, a is Random value, so b is also a random value now. After assigning a value to b, it is a's turn. At this time, the value of a comes from i, so a is 10.

2. Implicit type conversion

1. Implicit type conversion

In C++, our built-in types have implicit conversions, and similarly, our classes have such implicit conversions. If the constructor accepts only one argument, it actually defines an implicit conversion mechanism for this type. Sometimes we call this constructor a conversion constructor.

class A
{
    
    
public:
	A(int i = 0):_a(i)
	{
    
    
		cout << "A()" << endl;
	}
	void print()
	{
    
    
		cout << "A:" << _a << endl;
	}
private:
	int _a;
	int _b;
};
int main()
{
    
    
	A a1(10);
	// 用一个整形变量给A类对象赋值,实际编译器背后会用100构造一个无名对象,最后用无名对象给a2对象进行赋值
	A a2 = 100;
	a1.print();
	a2.print();
	return 0;
}

Insert image description here
The constructor can not only construct and initialize objects, but also has the function of type conversion for a single parameter or a constructor that has default values ​​except for the first parameter.

2.explicit

When we want to suppress the type conversion of the constructor, we need the explicit keyword

class A
{
    
    
public:
	explicit A(int i = 0) :_a(i)
	{
    
    
		cout << "A()" << endl;
	}
	void print()
	{
    
    
		cout << "A:" << _a << endl;
	}
private:
	int _a;
	int _b;
};
int main()
{
    
    
	A a1(10);
	A a2 = 100;
	a1.print();
	a2.print();
	return 0;
}

Insert image description here
The explicit keyword is only valid for constructors with one argument. Constructors that require multiple arguments cannot be used to perform implicit conversions. Therefore, constructors with multiple arguments do not need to be specified as explicit, and the constructor can only be declared within the class. The explicit keyword is used when functions are defined and should not be repeated when defined outside the class.
When we declare a constructor using the explicit keyword, it can only be used in the form of direct initialization. Furthermore, the compiler will not use this constructor during automatic conversion.

3. Delegate structure

The new C++11 standard extends the functionality of constructor initial values, allowing us to define so-called delegate constructors. A delegate constructor also has a list of member initial values ​​and a function body.
In the delegate constructor, the member initial value list has only one entry, which is the class name itself. Like other member initial values, the class name is followed by a parameter list enclosed in parentheses. The parameter list must be the same as another constructor in the class. Function matching.

class A
{
    
    
public:
	//非委托构造,使用对应的实参进行初始化成员
	A(int a,int b,int c) :_a(a),_b(b),_c(c)
	{
    
    
		cout << "A(int a,int b,int c)" << endl;
	}
	//委托构造
	A() : A(0,0,0) 
	{
    
    
		cout << "A()" << endl;
	}
	//委托构造
	A(int a) : A(a,0,0) 
	{
    
    
		cout << "A(int a)" << endl;
	}
	void print()
	{
    
    
		cout << "_a:" << _a << "  _b" << _b << "  _c" << _c << endl;
	}
private:
	int _a;
	int _b;
	int _c;
};
int main()
{
    
    
	A a1;
	A a2(10);
	a1.print();
	a2.print();
	return 0;
}

Insert image description here
The default construction here delegates it to the three-parameter constructor. It does not need to perform tasks. We can see from the results that the default construction is only executed after the three parameters are executed. The one-parameter constructor also delegates it to the three parameters. 's constructor. The delegated constructor executes before control is returned to the delegate's function body.

2. Static members of the class

Sometimes our class needs some of its member functions to be directly related to the class itself, rather than to each object of the class. At this time, we can declare the members of this class as static.

1. Static member declaration

Class members declared as static are called static members of the class. Member variables modified with static are called static member variables; member functions modified with static are called static member functions. Static member variables must be initialized outside the class.

class A
{
    
    
public:
	A();
	static void pid();//静态成员函数
private:
	int _a;
	int _b;
	static int id;//静态成员变量
};
int A::id = 0;//静态成员变量只能在类外进行初始化。

Static members of a class exist outside any object, and the object does not contain any data related to static members. We can also see from the size of the class in the previous article. Therefore, there can only be one id object, and it is shared by all A objects.
Similarly, static member functions cannot be bound to any object. They do not contain a this pointer. As a result, static member functions cannot be declared const. And we cannot use this pointer in the static function body.

2. Static member definition

When defining static members outside a class, the static keyword cannot be repeated. This keyword can only appear in the declaration statement inside the class.
Generally speaking, we cannot initialize static members inside a class. Instead, each static member must be defined and initialized outside the class. Like other objects, a static data member can only be defined once. The best way to ensure that an object is defined only once is to place the definition of static data members in the same file as the definition of other non-inline functions.

3. Static member characteristics

The characteristics of static members are as follows:

  1. Static members are shared by all class objects, do not belong to a specific object, and are stored in the static area.
  2. Static member variables must be defined outside the class. The static keyword is not added when defining. It is just declared in the class.
  3. Class static members can be accessed using class name::static member or object.static member
  4. Static member functions have no hidden this pointer and cannot access any non-static members.
  5. Static members are also members of the class and are restricted by public, protected, and private access qualifiers.

3. Overloaded operators and type conversion

In the last chapter, we already had a brief understanding of overloading. This time, let us learn about overloading in more depth.

1. Relational and arithmetic operator overloading

class A
{
    
    
public:
	A(int a = 0,int b = 0):_a(a),_b(b){
    
    }
	A operator+(const A& a1)//重载加号
	{
    
    
		A tmp;
		tmp._a = _a + a1._a;
		tmp._b = _b + a1._b;
		return tmp;
	}
	A& operator+=(const A& a1)//重载加等号
	{
    
    
		_a += a1._a;
		_b += a1._b;
		return *this;
	}
	void print()
	{
    
    
		cout << "_a:" << _a << "  _b:" << _b << endl;
	}
private:
	int _a;
	int _b;
};
int main()
{
    
    
	A a1(10, 20);
	A a2(30, 40);
	cout << "a1:";
	a1.print();
	cout << "a2:";
	a2.print();
	A a3 = a1 + a2;
	a1 += a2;
	cout << "a1:";
	a1.print();
	cout << "a3:";
	a3.print();
}

Insert image description here
Why is it more effective to call operator+= to replace operator+?
Because + needs to create a temporary object, while += only has one object.

class A
{
    
    
public:
	A(int a = 0, int b = 0) :_a(a), _b(b) {
    
    }
	bool operator<(const A& a1)//重载小于号
	{
    
    
		return (_a < a1._a) && (_b < a1._b);
	}
private:
	int _a;
	int _b;
};
int main()
{
    
    
	A a1(10, 20);
	A a2(30, 40);
	if (a1 < a2)
	{
    
    
		cout << "TRUE" << endl;
	}
	return 0;
}

Insert image description here
Here we use the method that returns true only when each object member in the object is smaller than another object.

2. Increment and decrement operator overloading and how to distinguish them

class A
{
    
    
public:
	A(int a = 0, int b = 0) :_a(a), _b(b) {
    
    }
	A& operator++()//重载前置++
	{
    
    
		_a++;
		_b++;
		return *this;
	}
	A& operator++(int)//重载后置++
	{
    
    
		A tmp = *this;
		_a++;
		_b++;
		return tmp;
	}
	void print()
	{
    
    
		cout << "_a:" << _a << "  _b:" << _b << endl;
	}
private:
	int _a;
	int _b;
};
int main()
{
    
    
	A a1(10, 20);
	A a2(30, 40);
	++a1;//前置++
	a1.print();
	A a3 = a2++;
	a3.print();
	return 0;
}

Insert image description here
We found that post++ has one more parameter, but we did not pass the parameter. How does the compiler know that we want to call postfix?
The postfix version accepts an additional (unused) int parameter. When we use the postfix operator, the compiler will provide an actual parameter with a value of 0. Although syntactically our post-function will use this extra parameter, in practice this is usually not done. The only function of this formal parameter is to distinguish between the prefixed version and the postfixed version, rather than actually participating in the operation.
You can implement pre-- and post-- by yourself;

3. Assignment operator overloading

class A
{
    
    
public:
	A(int a = 0, int b = 0) :_a(a), _b(b) {
    
    }
	A& operator=(const A& a1)
	{
    
    
		_a = a1._a;
		_b = a1._b;
		return *this;
	}
	void print()
	{
    
    
		cout << "_a:" << _a << "  _b:" << _b << endl;
	}
private:
	int _a;
	int _b;
};
int main()
{
    
    
	A a1(10, 20);
	A a2;
	a2 = a1;
	a2.print();
	return 0;
}

Insert image description here
We have already seen this in the previous article. It should be noted that assignment operators must be defined as member functions.

4. Overloading input and output operators

1. Overloading input operators

Normally, the first parameter of the output operator is a reference to a non-constant ostream object. The reason why ostream is non-const is that writing to the stream changes its state: and the parameter is a reference because we cannot directly copy an ostream object.

class A
{
    
    
public:
	A(int a = 0, int b = 0) :_a(a), _b(b) {
    
    }
	ostream& operator<<(ostream &os)
	{
    
    
		os << "_a:" << _a << "  _b:" << _b << endl;
		return os;
	}
private:
	int _a;
	int _b;
};
int main()
{
    
    
	A a1(10, 20);
	//cout << a1;
	a1 << cout;
	return 0;
}

Insert image description here
Insert image description here
We found that the way of printing is different from the way we normally use it. Why is this?
Because one parameter of the function we call is the implicit this pointer, our call must have our class object first.
To be compatible with the standard library, our input and output operators must be ordinary non-member functions, not member functions of the class.

class A
{
    
    
public:
	friend ostream& operator<<(ostream& os, A& a1);
	A(int a = 0, int b = 0) :_a(a), _b(b) {
    
    }
private:
	int _a;
	int _b;
};
ostream& operator<<(ostream& os, A& a1)
{
    
    
	os << "_a:" << a1._a << "  _b:" << a1._b << endl;
	return os;
}
int main()
{
    
    
	A a1(10, 20);
	A a2(20, 40);
	cout << a1 << a2 << endl;
	return 0;
}

Insert image description here
We need to ensure that the returned type is also a reference to ostream, so that continuous output can be achieved.

2. Overloading output operators

class A
{
    
    
public:
	friend istream& operator>>(istream& is, A& a1);
	friend ostream& operator<<(ostream& os, A& a1);
	A(int a = 0, int b = 0) :_a(a), _b(b) {
    
    }
private:
	int _a;
	int _b;
};
istream& operator>>(istream& is, A& a1)
{
    
    
	int a = 0;
	int b = 0;
	is >> a >> b;
	if (is)//检查输入是否成功
	{
    
    
		a1._a = a;
		a1._b = b;
	}
	else
	{
    
    
		a1 = A();//输入失败,对象被赋予默认状态
	}
	return is;
}
ostream& operator<<(ostream& os, A& a1)
{
    
    
	os << "_a:" << a1._a << "  _b:" << a1._b << endl;
	return os;
}
int main()
{
    
    
	A a1;
	cin >> a1;
	cout << a1 << endl;
	return 0;
}

Insert image description here
Input operators must handle situations where input may fail, while output operators do not. When an error occurs in our read operation, the input operator should be responsible for recovering from the error.

5. Function matching and overloaded operators

Overloaded operators are also overloaded functions. Therefore, the general function matching rules also apply to determine whether a built-in type operator or an overloaded operator should be used in a given expression.
When we call a named function, the member functions and non-member functions with that name do not overload each other. This is because the syntax form we use to call the named function is different for member functions and non-member functions. When we make a function call through an object of a class type (or a pointer or reference to the object), only the member functions of the class are considered. When we use overloaded operators in expressions, we cannot tell whether we are using a member function or a non-member function, so both should be considered.
The set of candidate functions for an operator in an expression should include both member functions and non-member functions. We cannot provide both a conversion target to an arithmetic type and an overloaded operator for a class, as this will cause ambiguity.

4. Other details of the class

1.const member function

The const-modified "member function" is called a const member function. The const-modified class member function actually modifies the this pointer implicit in the member function, indicating that no member of the class can be modified in the member function.

class A
{
    
    
public:
	A(int a = 0, int b = 0) :_a(a), _b(b) {
    
    }
	void print() const
	{
    
    
		cout << "_a:" << _a << "  _b:" << _b << endl;
	}
private:
	int _a;
	int _b;
};
int main()
{
    
    
	A a1;
	return 0;
}

Since we don't want to modify the object, but we can't add const to the formal parameters, we add const after the formal parameter list. The
Insert image description here
compiler will perform the above processing on const.

2.Inner class

If a class is defined inside another class, the inner class is called an inner class. The inner class is an independent class. It does not belong to the outer class, and the members of the inner class cannot be accessed through the objects of the outer class. The outer class does not have any superior access to the inner class.
Note:
The inner class is the friend class of the outer class. See the definition of friend class. The inner class can access all members of the outer class through the object parameters of the outer class. But the outer class is not a friend of the inner class.

Inner class properties

The characteristics of inner classes are as follows:

  1. Inner classes can be defined as public, protected, or private in external classes.
  2. Note that inner classes can directly access static members in outer classes without requiring the object/class name of the outer class.
  3. sizeof(external class)=external class, has nothing to do with internal classes.
class A
{
    
    
public:
	class B 
	{
    
    
	public:
		void print(const A& a)
		{
    
    
			cout << "A::_a:" << a._a << "   A::_b:" << a._b << "   B::id:" << id << endl;
		}
	private:
		int id = 0;
	};
private:
	int _a = 0;
	int _b = 0;
};
int main()
{
    
    
	A::B b;
	b.print(A());
	cout <<"A的大小:" << sizeof(A) << endl;
	return 0;
}

Insert image description here

3. Anonymous objects

class A
{
    
    
public:
	A(int a = 0):_a(a)
	{
    
    
		cout << "A()" << endl;
	}
	~A()
	{
    
    
		cout << "~A()" << endl;
	}
private:
	int _a;
};
int main()
{
    
    
	A aa1;
	cout << "到匿名对象了" << endl;
	A();// 我们可以这么定义匿名对象,匿名对象的特点不用取名字
	// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
	cout << "匿名对象结束了" << endl;
	A aa2(2);
	return 0;
}

Insert image description here
Note: The life cycle of anonymous objects has only this line

4. Compiler optimization during copying

In the process of passing parameters and return values, the compiler will generally do some optimizations to reduce object copies. Let's take a look at the compiler's optimizations.

class A
{
    
    
public:
	A(int a = 0):_a(a)
	{
    
    
		cout << "A(int a)" << endl;
	}
	A(const A& aa):_a(aa._a)
	{
    
    
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
    
    
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
    
    
			_a = aa._a;
		}
		return *this;
	}
	~A()
	{
    
    
		cout << "~A()" << endl;
	}
private:
	int _a;
};
void fun1(A aa){
    
    }
A fun2()
{
    
    
	A aa;
	return aa;
}
int main()
{
    
    
	//传值传参
	A aa1;
	fun1(aa1);
	cout << endl;
	//传值返回
	fun2();
	cout << endl;
	// 隐式类型,连续构造+拷贝构造->优化为直接构造
	fun1(1);
	// 一个表达式中,连续构造+拷贝构造->优化为一个构造
	fun1(A(2));
	cout << endl;
	// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
	A aa2 = fun2();
	cout << endl;
	// 一个表达式中,连续拷贝构造+赋值重载->无法优化
	aa1 = fun2();
	cout << endl;
	return 0;
}

Insert image description here
We should try to put the constructor and copy into one sentence, which can slightly improve our efficiency.

5. Further understanding of packaging

A class describes a certain type of entity (object), describing what attributes and methods the object has. After the description is completed, a new custom type is formed, and the specific type can be instantiated using this custom type. Object.
Insert image description here

Summarize

At this point our class has started. If we want to learn more about classes, we also need to learn inheritance and polymorphism. Further learning requires us to be proficient in using the content of the classes we have learned now, so in the next step we will enter the study of containers.

Guess you like

Origin blog.csdn.net/2301_76986069/article/details/132891998