C++ classes and objects - 4 (initialization list, Static members, friends, inner classes, anonymous objects)

1. Let’s talk about the constructor again

1.1 Constructor body assignment

When creating an object, the compiler gives an appropriate initial value to each member variable in the object by calling the constructor.

class Date
{
    
    
public:
    Date(int year, int month, int day)
    {
    
    
        _year = year;
        _month = month;
        _day = day;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    
    
    Date d1(2023, 12, 31);
    Date d2(2022, 1, 1);
    return 0;
}

Insert image description here

Although the object already has an initial value after the above constructor is called, it cannot be called initialization of member variables in the object. The statements in the constructor body can only be called initial value assignment, not initialization. initialization. Because initialization can only be initialized once, and the constructor body can be assigned multiple times.

1.2 Initialization list (emphasis)

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.

class Date
{
    
    
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{
    
    }
private:
	int _year;
	int _month;
	int _day;
};

【Notice】

1. Each member variable can only appear once in the initialization list (initialization can only be initialized once)
2. The class contains the following members, which must be placed in the initialization list for initialization:

  • Reference member variables
  • const member variable
  • Custom type members (when the class does not have a default constructor)
class A
{
    
    
public:
	A(int a)
		:_a(a)
	{
    
    }
private:
	int _a;
};
class B
{
    
    
public:
	B(int a, int ref)
		:_aobj(a)
		, _ref(ref)
		, _n(10)
	{
    
    }
private:
	A _aobj;  // 没有默认构造函数
	int& _ref;  // 引用
	const int _n; // const 
};

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.

class Time
{
    
    
public:
	Time(int hour = 0)
		:_hour(hour)
	{
    
    
		cout << "Time()" << endl;
	}
private:
	int _hour;
};
class Date
{
    
    
public:
	Date(int day)
	{
    
    }
private:
	int _day;
	Time _t;
};
int main()
{
    
    
	Date d(1);
}

operation result:
Insert image description here

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.

Example:
The result of running the following code is:
A. Output 1 (and) 1
B. Program crash
C. Failure to compile
D. Output 1 (and) random value

class A
{
    
    
public:
    A(int a)
       :_a1(a)
       ,_a2(_a1)
   {
    
    }
    
    void Print() {
    
    
        cout<<_a1<<" "<<_a2<<endl;
   }
private:
    int _a2;
    int _a1;
};
int main() {
    
    
    A aa(1);
    aa.Print();
}

Running results:

In the above code, the initialization of _a2 depends on _a1, but the order of declaration in the class is _a2 before _a1. Therefore, _a2 will be initialized first, and it will use the uninitialized value of _a1, which is random because it has not been initialized yet.
Then, _a1 is assigned the parameter value of the constructor in the initialization list, which is 1.
Therefore, the output result is 1 and a random value, choose D.


1.3 explicit keyword

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.

class Date
{
    
    
public:
	// 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
 // explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译
	explicit Date(int year)
		:_year(year)
	{
    
    }
	
	//2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具
     //有类型转换作用
	 //explicit修饰构造函数,禁止类型转换
	/* explicit Date(int year, int month = 1, int day = 1)
	: _year(year)
	, _month(month)
	, _day(day)
	{}*/
	
	Date& operator=(const Date& d)
	{
    
    
		if (this != &d)
		{
    
    
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};
void Test()
{
    
    
	Date d1(2022);
	// 用一个整形变量给日期类型对象赋值
	
	// 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值
	d1 = 2023;
	// 将1屏蔽掉,2放开时则编译失败,因为explicit修饰构造函数,禁止了单参构造函数类型转
	//换的作用
}
int main()
{
    
    
	Test();
	return 0;
}

Insert image description here

The above code is not very readable. Modifying the constructor with explicit will prohibit the implicit conversion of the constructor.

2. Static members

2.1 Concept

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() {
    
     ++_scount; }
	A(const A& t) {
    
     ++_scount; }
	~A() {
    
     --_scount; }
	static int GetACount() {
    
     return _scount; }
private:
	static int _scount;
};
int A::_scount = 0;
void TestA()
{
    
    
	cout << A::GetACount() << endl;
	A a1, a2;
	A a3(a1);
	cout << A::GetACount() << endl;
}
int main()
{
    
    
	TestA();
	cout << A::GetACount() << endl;
	return 0;
}

Running results:
Insert image description here
Explanation:

In the main() function:
TestA() is called, it will output that the initial number of instances is 0, then create two instances of class A (a1 and a2), and use a1 to perform copy construction to create a new instance (a3 ).
After the TestA() function is called, the static member _scount of the output class A is 3.
At the end of the function call, the automatic destructor ~(A) is called, --_scount, destructed three times, and finally the static member _scount of class A is 0 again

2.2 Features

1. Static members are shared by all class objects and do not belong to a specific object. They 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 do not have a 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.

[Question]
1. Can static member functions call non-static member functions?

No: Static member functions are functions that belong to the class itself rather than to instances of the class . They cannot directly access non-static member functions because non-static member functions depend on instances of the class.

2. Can non-static member functions call static member functions of a class?

Yes: Non-static member functions can call static member functions of the class . Non-static member functions depend on the instance of the class, while static member functions belong to the class itself, so non-static member functions can access and call static member functions directly through the class name . Inside a non-static member function, a static member function can be called using the class name or an instance of the class.

3. Tomomoto

Friends provide a way to break through encapsulation and sometimes provide convenience. However, friends will increase coupling and destroy encapsulation, so friends should not be used more than once.

Friends are divided into: friend functions and friend classes

3.1 Friend functions

Problem: Now I try to overload operator<< , and then I find that there is no way to overload operator<< into a member function.

Because cout's output stream object and the implicit this pointer are preempting the position of the first parameter. The this pointer defaults to the first parameter, which is the left operand . However, in actual use , cout needs to be the first formal parameter object to be used normally. So operator<< needs to be overloaded into a global function . But it will also cause members outside the class to be unable to access. At this time, friends are needed to solve the problem. operator>>Similarly.

The following code illustrates why operator<< cannot be overloaded into a member function:

class Date
{
    
    
public:
    Date(int year, int month, int day)
        : _year(year)
        , _month(month)
        , _day(day)
    {
    
    }
    // d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用
    // 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
    ostream& operator<<(ostream& _cout)
    {
    
    
        _cout << _year << "-" << _month << "-" << _day << endl;
        return _cout;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    
    
    Date d1(1,1,1);
    d1 << cout;// d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用
    return 0;
}

Insert image description here
Although the call result is correct, [d1 << cout;] does not conform to conventional calls.

A friend function can directly access the private members of a class . It is an ordinary function defined outside the class and does not belong to any class. However, it needs to be declared inside the class. The friend keyword needs to be added when declaring .

class Date
{
    
    
	friend ostream& operator<<(ostream& _cout, const Date& d);
	friend istream& operator>>(istream& _cin, Date& d);
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{
    
    }
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
    
    
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
    
    
	_cin >> d._year;
	_cin >> d._month;
	_cin >> d._day;
	return _cin;
}
int main()
{
    
    
	Date d;
	cin >> d;
	cout << d << endl;
	return 0;
}

illustrate:

  • Friend functions can access private and protected members of a class, but not member functions of the class
  • Friend functions cannot be modified with const
  • Friend functions can be declared anywhere in a class definition and are not restricted by class access qualifiers.
  • A function can be a friend function of multiple classes
  • The principle of calling friend functions is the same as that of ordinary functions.

3.2 Friend classes

  • All member functions of a friend class can be friend functions of another class and can access non-public members of another class.
  • Friend relationships are one-way and non-commutative.
    For example, in the above Time class and Date class, if you declare the Date class as its friend class in the Time class, you can directly access the private member variables of the Time class in the Date class, but you want to access the private member variables of the Date class in the Time class. No.
    (I treat you as a friend, you treat me as a tool)
  • The friend relationship is not transitive.
    If C is a friend of B and B is a friend of A, it cannot be said that C is a friend of A.
class Time
{
    
    
    friend class Date;// 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
    Time(int hour = 0, int minute = 0, int second = 0)
        : _hour(hour)
        , _minute(minute)
        , _second(second)
    {
    
    }
private:
    int _hour;
    int _minute;
    int _second;
    Date _d;
};
class Date
{
    
    
    
public:
    Date(int year = 1900, int month = 1, int day = 1)
        : _year(year)
        , _month(month)
        , _day(day)
    {
    
    }

    void SetTimeOfDate(int hour, int minute, int second)
    {
    
    
        // 直接访问时间类私有的成员变量
        _t._hour = hour;
        _t._minute = minute;
        _t._second = second;
    }

private:
    int _year;
    int _month;
    int _day;
    Time _t;
};

int main()
{
    
    
    Date d1;
    d1.SetTimeOfDate(20, 33, 0434);
    return 0;
}

4. Inner classes

Concept: If a class is defined inside another class, this 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.

characteristic:

1. Inner classes can be defined as public, protected, or private in external classes.
2. Note that the inner class can directly access the static members in the outer class without the object/class name of the outer class.
3. sizeof (external class) = external class, has nothing to do with internal classes.

class A
{
    
    
private:
    static int k;
    int h;

public:
    class B // B天生就是A的友元
    {
    
    
    public:
        void foo(const A& a)
        {
    
    
            cout << k << endl;//OK
            cout <<a.h << endl;//OK
            cout << a.k << endl;//OK
        }
    };
};
int A::k = 1;
int main()
{
    
    
    A::B b;
    b.foo(A());

    return 0;
}

5. Anonymous objects

An anonymous object means that the object is not named when it is created, but is used directly in expressions or as parameters of functions. In C++, you can create an anonymous object by appending a pair of parentheses after the class name.
Anonymous objects are often used for short, temporary operations to avoid explicitly defining a variable, thereby reducing code complexity and memory overhead . They are useful in certain situations, such as operations on temporary objects or return values ​​from functions.

class A
{
    
    
public:
	A(int a = 0)
		:_a(a)
	{
    
    
		cout << "A(int a)" << endl;
	}

~A()
{
    
    
	cout << "~A()" << endl;
}
private:
	int _a;
};
class Solution {
    
    
public:
	int Sum_Solution(int n) {
    
    
		//...
		return n;
	}
};
int main()
{
    
    
	A aa1;

	// 不能像下面这行这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
	//A aa1();
	// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
	// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
	A();
	A aa2(2);
	// 匿名对象在这样场景下就很好用。
	Solution().Sum_Solution(10);
	return 0;
}

Solution().Sum_Solution(10);】This statement creates an anonymous Solution-like object and calls its member function Sum_Solution(10). This is an anonymous object whose lifetime is only as long as this line of code is executed.

Summarize:

Anonymous objects in C++ can be used for some short temporary operations, and their life cycle is limited to the execution period of the current line . Anonymous objects typically perform one-time operations without the need for named objects, and avoid the complexity and memory overhead of explicitly defining variables . However, it should be noted that in some cases, the life cycle of anonymous objects may not be as expected, so you should be careful when using it.

(End of chapter)

Guess you like

Origin blog.csdn.net/originalHSL/article/details/132012821