Technology Sharing

C Basics (II)

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Table of contents

1. Classes and Objects

1.1 Class Definition

1.2 Access Qualifiers

1.3 Class Domain

2. Instantiation

2.1 Instantiation Concept

2.2 Object Size

3.this pointer

4. Default member functions of a class

4.1 Constructor

4.2 Destructor

4.5 Operator Overloading


1. Classes and Objects

1.1 Class Definition

Class definition format

class is the keyword for defining a class, Stack is the name of the class, and {} is the body of the class. Note that the semicolon at the end of the definition is not omitted. The content in the class body is called the members of the class: the variables in the class are called the attributes or member variables of the class; the functions in the class are called the methods or member functions of the class.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. //成员变量
  7. int* a;
  8. int top;
  9. int capacity;
  10. //成员函数
  11. void Push()
  12. {
  13. }
  14. void Pop()
  15. {
  16. }
  17. };//分号不能省略
  18. int main()
  19. {
  20. return 0;
  21. }

  • In order to distinguish member variables, it is customary to add a special identifier to member variables, such as adding _ or m_ before or after the member variable. This is not specified in C++ syntax and is based on personal or company preference.
  1. //为区分成员变量,一般前面加_
  2. //成员变量
  3. int* _a;
  4. int _top;
  5. int _capacity;
  • In C++, struct can also define classes. C++ is compatible with the usage of struct in C. At the same time, struct has been upgraded to class. The obvious change is that functions can also be defined in struct. In general, we still recommend using class to define classes.

  • Members defined in a class are inline by default.

1.2 Access Qualifiers

A way to implement encapsulation in C++, using classes to combine the properties and methods of an object to make the object more complete, and selectively provide its interface to external users through access permissions.

  • The members modified by public can be directly accessed outside the class, while the members modified by protected and private cannot be directly accessed outside the class. protected and private are the same.
  • The scope of the access right starts from the position where the access right appears until the next access qualifier appears. If there is no access qualifier after it, the scope ends at }, that is, the class ends.
  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. ///
  7. void Push()
  8. {
  9. }
  10. //Push 没给限定符 class默认私有 private
  11. ///
  12. public:
  13. void Pop()
  14. {
  15. }
  16. int Swap()
  17. {
  18. }
  19. //Pop和Swap 被public修饰,直到下一个限定符出现之前都为公有
  20. ///
  21. protected:
  22. int add();
  23. //add 被public修饰,直到下一个限定符出现之前都为保护
  24. /// /
  25. private:
  26. int* _a;
  27. int _top;
  28. int _capacity;
  29. //成员变量被private修饰,直到}结束都为私有
  30. };
  31. int main()
  32. {
  33. Stack st;
  34. //公有可以访问
  35. st.Pop();
  36. st.Swap();
  37. //私有不可访问
  38. st._top;
  39. return 0;
  40. }

Additional Note:

  1. When a class member is not modified by an access qualifier, it is private by default, and a struct member is public by default.
  2. Generally, member variables are restricted to private/protected, and member functions that need to be used by others are placed as public.

1.3 Class Domain

A class defines a new scope. All members of the class are in the scope of the class. When defining members outside the class, you need to use the :: scope operator to indicate which class scope the member belongs to.

The class scope affects the search rules of the compiler. In the following program, if the class scope Stack is not specified for Init, the compiler will treat Init as a global function. If it cannot find members such as _top during compilation, it will look for them in the class scope.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. public:
  7. void Init(int x, int y);
  8. };
  9. void Stack::Init(int x, int y)
  10. {
  11. _top = x;
  12. _capacity = y;
  13. }
  14. int main()
  15. {
  16. return 0;
  17. }

Notice:

  1. The function declarations and definitions in the class are separated. After the class is created, a new class domain is formed. The class domain needs to be specified, otherwise it is inaccessible.

2. Instantiation

2.1 Instantiation Concept

  • The process of creating an object in physical memory using a type is called class instantiation.
  • A class is an abstract description of an object. It is a model-like thing that limits the member variables of a class. These member variables are only declared without any space allocated. Space is allocated only when an object is instantiated using the class.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. //声明
  7. int* _a;
  8. int _top;
  9. int _capacity;
  10. };
  11. int main()
  12. {
  13. Stack::_top = 2024;
  14. //编译器报错,_top只是声明,并未实例化
  15. return 0;
  16. }
  • A class can instantiate multiple objects. The instantiated objects occupy actual physical space and store member variables. For example, instantiating objects from a class is like using architectural blueprints to build a house in reality. The class is like a blueprint. The blueprint plans out how many rooms there are, the size of the house, etc., but there is no physical building and people cannot live in it. Only when the house is built using the blueprint can people live in it. Similarly, a class is like a blueprint. It only tells the compiler how much memory to open, but does not open the memory. Only the instantiated objects allocate physical memory to store data.
    1. //text.cpp
    2. #include<iostream>
    3. using namespace std;
    4. class Stack
    5. {
    6. //声明
    7. int* _a;
    8. int _top;
    9. int _capacity;
    10. };
    11. int main()
    12. {
    13. Stack st;
    14. st._top=2024;
    15. //Stack实例化出st,系统已经给st分配内存了,可以存储数据,编译通过
    16. return 0;
    17. }

    2.2 Object Size

  • Let's analyze what members a class object has? Each object instantiated from a class has an independent data space, so the object must contain member variables, but does it include member functions? First of all, after a function is compiled, it is a section of instructions, which cannot be stored in an object. These instructions are stored in a separate area (code segment), so if the object must be stored, it can only be a pointer to a member function. Is it necessary to store pointers in an object? Date instantiates two objects d1 and d2. Both di and d2 have their own independent member variables _year/_month/_day to store their own data, but the member function Init/Print pointers of d1 and d2 are the same, so it is a waste to store them in the object. If Date is used to instantiate 100 objects, the member function pointers will be stored repeatedly 100 times, which is a waste. In fact, function pointers do not need to be stored. Function pointers are an address. Calling a function is compiled into an assembly instruction [call address]. In fact, the compiler must find the address of the function when compiling and linking, not at runtime. Only dynamic polymorphism is found at runtime, so the function address needs to be stored.

Memory alignment rules

  • The first member is at offset 0 from the structure.
  • Other member variables should be aligned to addresses that are integer multiples of the alignment number.
  • Alignment = the smaller value of the compiler's default alignment and the size of the member
  • The default alignment number for VS x64 platform is 4, and the default alignment number for x86 is 8
  • The total size of the structure is an integer multiple of the maximum alignment number (the minimum of the maximum of all type variables and the default alignment number)
  • If structures are nested, the nested structures are aligned to integer multiples of their own maximum alignment numbers, and the overall size of the structure is an integer multiple of all maximum alignment numbers (including the alignment numbers of nested structures)
  1. class A
  2. {
  3. public:
  4. void Print()
  5. {
  6. cout << _ch << endl;
  7. }
  8. private:
  9. char _ch;
  10. int _i;
  11. };
  12. //_ch 是一个字节,默认对齐数是4,最大对齐数是4,所以开辟4个字节用来存在_ch
  13. // _i是4个字节,默认对齐数是4,最大对齐数是4,所以开辟4个字节用来存储_i
  14. class B
  15. {
  16. public:
  17. void Print()
  18. {
  19. //。。。
  20. }
  21. };
  22. class B
  23. {
  24. };
  25. //B和C里面没有存储任何成员变量,只有一个函数,可成员函数不存对象里面
  26. // 按理来说是0,但是结构体怎么会没大小,为表示对象存在C++对这种规定大小为1,为了占位标识对象存在

3.this pointer

After the compiler compiles, the member functions of the class will add a pointer to the current class in the first position of the formal parameter by default, called the this pointer

For example, the prototype of Init in the Date class is void Init (Date * const this, int year, int month, int day). The member variables in the member functions of the class are accessed through the this pointer. For example, in the Init function, _year is assigned a value, this-&gt;_year=year

prototype:

  1. class Date
  2. {
  3. void Print()
  4. {
  5. cout << _year << "n" << _month << "n" << _day << endl;
  6. }
  7. void Init( int year, int month,int day)
  8. {
  9. _year = year;
  10. _month = month;
  11. _day = day;
  12. }
  13. private:
  14. int _year;
  15. int _month;
  16. int _day;
  17. };

  1. Date d1;
  2. d1.Init(2024,7,10);
  3. d1.Print();
  4. Date d2;
  5. d2.Init(2024, 7, 9);
  6. d2.Print();

Real prototype

  1. class Date
  2. {
  3. void Init(Date* const this,int year, int month,int day)
  4. {
  5. this->_year = year;
  6. this->_month = month;
  7. this->_day = day;
  8. }
  9. void Printf(Date* const this)
  10. {
  11. cout << this->_year << "n" <<this-> _month << "n" << this->_day << endl;
  12. }
  13. private:
  14. int _year;
  15. int _month;
  16. int _day;
  17. };

  1. Date d1;
  2. d1.Init(&d1,2024,7,10);
  3. d1.Print(&d1);
  4. Date d2;
  5. d2.Init(&d2,2024, 7, 9);
  6. d2.Print();

C++ stipulates that it is not allowed to write this pointer in the position of actual parameters and formal parameters (the compiler will handle it during compilation), but you can use this pointer explicitly in the function body. This pointer cannot be modified, but the content pointed to by this pointer can be modified.

This pointer is stored in the stack

4. Default member functions of a class

The default member function is a member function that is automatically generated by the compiler without explicit definition by the user.

4.1 Constructor

The constructor is a special member function. It should be noted that although the name of the constructor is a constructor, the main content of the constructor is not to create space for objects (the local objects we usually use are created when the stack frame is created, and the space is already created), but to initialize the object when the object is instantiated. The essence of the constructor is to replace the function of the Init function we wrote in the Stack and Date classes before. The automatic call feature of the constructor perfectly replaces the Init function.

Features of the constructor:

  1. The function name is the same as the class name
  2. No return value (no return value is needed, and don't write void, that's how C++ is defined)
  3. When an object is instantiated, the system will automatically call the corresponding constructor
  4. Constructors can be overloaded
  5. If the class does not explicitly define a constructor, the C++ compiler will automatically generate a default constructor without parameters. Once the user explicitly defines it, the compiler will no longer generate a default constructor.

  1. class Date
  2. {public:
  3. //1.无参构造函数
  4. Date()
  5. {
  6. _year = 1;
  7. _month = 1;
  8. _day = 1;
  9. }
  10. //2.带参构造函数
  11. Date(int year, int month, int day)
  12. {
  13. _year = year;
  14. _month = month;
  15. _day = day;
  16. }
  17. //3.全缺省构造函数
  18. Date(int year = 1, int month = 1, int day = 1)
  19. {
  20. _year = year;
  21. _month = month;
  22. _day = day;
  23. }
  24. private:
  25. int _year;
  26. int _month;
  27. int _day;
  28. };

The no-parameter constructor, the full default constructor, and the constructor generated by the compiler by default when we do not write a constructor are all called default constructors. However, only one of these three can exist, and they cannot exist at the same time. Although the no-parameter constructor and the full default constructor constitute function overloading, there will be ambiguity when calling. Note that the default constructor is not the only constructor generated by the compiler by default. The no-parameter constructor and the full default constructor are also default constructors. In summary, they can be called without passing parameters.

If we don't write it, the compiler generates the structure by default, which has no requirements for the initialization of member variables of built-in types. In other words, whether to initialize is uncertain and depends on the compiler.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. typedef int STDataType;
  5. class Stack
  6. {
  7. public:
  8. Stack(int n = 4)
  9. {
  10. _a = (STDataType*)malloc(sizeof(STDataType) * n);
  11. if (nullptr == _a)
  12. {
  13. perror("malloc申请失败");
  14. }
  15. _capacity = n;
  16. _top = 0;
  17. }
  18. private:
  19. STDataType* _a;
  20. size_t _capacity;
  21. size_t _top;
  22. };
  23. //两个Stack实现队列
  24. class MyQueue
  25. {
  26. private:
  27. int size;
  28. Stack pushst;
  29. Stack popst;
  30. };
  31. int main()
  32. {
  33. MyQueue my;
  34. return 0;
  35. }

C++ divides types into custom types and built-in types (basic types). Built-in types are native data types provided by the language, such as int/char/double/pointer, etc. Custom types are types that we define ourselves using keywords such as class/struct. The constructor is automatically initialized here, and VS also initializes the built-in type size. Different compilers have different initialization values, and C++ does not specify

For custom type member variables, it is required to call the default constructor of this member variable for initialization. If this member variable does not have a default constructor, an error will be reported. To initialize this member variable, an initialization list is required to solve it.

Summary: In most cases, we need to implement the constructor ourselves. In a few cases like MyQueue and Stack has a default constructor, MyQueue can be automatically generated and used.

4.2 Destructor

  1. ~Stack()
  2. {
  3. free(_a);
  4. _a = nullptr;
  5. _top = _capacity = 0;
  6. }

Characteristics of destructor:

1. The destructor name is the character ~ added before the class name

2. No parameters and no return value (same as the constructor)

3. A class can only have one destructor. If it is not explicitly defined, the system will automatically generate a default destructor.

4. When the object declaration cycle ends, the system automatically calls the destructor.

5. Similar to the constructor, we do not write the destructor automatically generated by the compiler. We do not process the built-in type members. Custom type members will call other destructors.

6. It should also be noted that when we display the destructor, the destructor of the custom type member will also be called, that is, the destructor of the custom type member will be automatically called no matter what the situation.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. typedef int STDataType;
  5. class Stack
  6. {
  7. public:
  8. Stack(int n = 4)
  9. {
  10. _a = (STDataType*)malloc(sizeof(STDataType) * n);
  11. if (nullptr == _a)
  12. {
  13. perror("malloc申请失败");
  14. }
  15. _capacity = n;
  16. _top = 0;
  17. }
  18. ~Stack()
  19. {
  20. free(_a);
  21. _a = nullptr;
  22. _top=_capacity=0;
  23. }
  24. private:
  25. STDataType* _a;
  26. size_t _capacity;
  27. size_t _top;
  28. };
  29. //两个Stack实现队列
  30. class MyQueue
  31. {public:
  32. //编译器默认生成MyQueue的构造函数调用了Stack的构造,完成了两个成员的初始化
  33. //编译器默认生成MyQueue的析构函数调用了Stack的析构,释放了Stack内部的资源
  34. //显示写析构也会调用Stack的析构
  35. ~MyQueue()
  36. {
  37. cout << "~MyQueue" << endl;
  38. }
  39. private:
  40. Stack pushst;
  41. Stack popst;
  42. };
  43. int main()
  44. {
  45. MyQueue my;
  46. return 0;
  47. }

The destructor in MyQueue does nothing, but C++ requires calling other destructors to release memory.

If there is no resource application, the destructor can be omitted and the default destructor generated by the compiler can be used directly, such as Date. If the default generated destructor can be used, there is no need to explicitly write the destructor, such as MyQueue. However, when there is a resource application, the destructor must be written directly, otherwise it will cause resource leakage, such as Stack

4.5 Operator Overloading

  • When an operator is used on an object of a type, the C++ language allows us to specify a new meaning through operator overloading. C++ stipulates that when a class type object uses an operator, it must be converted to a call to the corresponding operator overload. If not, the compiler will report an error.
  • An operator overload is a function with a specific name. Its name is composed of operator and the operator to be defined later. Like other functions, it also has its return type and parameter list and function body.
  1. bool operator<(Date d1, Date d2)
  2. {
  3. }
  4. bool operator==(Date d1,Date d2)
  5. {
  6. return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
  7. }
  • The number of parameters of an overloaded operator function is the same as the number of parameters that the operator acts on. Unary operators have one parameter, and binary operators have two parameters. The left operand of a binary operator is passed to the first parameter, and the right operand is passed to the second parameter.
  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Date
  5. {
  6. public:
  7. Date(int year, int month, int day)
  8. {
  9. _year= year;
  10. _month = month;
  11. _day = day;
  12. }
  13. int _year;
  14. int _month;
  15. int _day;
  16. };
  17. bool operator<(Date d1, Date d2)
  18. {
  19. }
  20. bool operator==(Date d1,Date d2)
  21. {
  22. return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
  23. }
  24. int main()
  25. {
  26. Date d1(2024, 7, 10);
  27. Date d2(2024,7,9);
  28. //两种用法都可以
  29. d1 == d2;
  30. operator==(d1 , d2);
  31. return 0;
  32. }
  • If an overloaded operator function is a member function, its first operand is passed to the implicit this pointer by default. Therefore, when an operator overload is used as a member function, the parameter is one less than the operand.
  • After the operator is overloaded, its priority and associativity remain the same as those of the built-in type operations.
  • You cannot create operators by concatenating symbols that are not in the syntax: for example, operator@