<C++> Classes and Objects - Object Oriented

1. Object-oriented

The C language is process-oriented , focusing on the process, analyzing the steps to solve the problem, and solving the problem step by step through function calls.

C++ is based on object-oriented , focusing on objects, splitting one thing into different objects, and relying on the interaction between objects to complete.

2. class

Only variables can be defined in the C language structure. In C++, not only variables but also functions can be defined in the structure. ,

C++ is compatible with C structure usage, and C++ upgrades structure to class.

Example:

typedef struct ListNode {
    
    
    int val;
    struct ListNode *next;
} LTN;

// C struct ListNode是类型

struct ListNode {
    
    
    int val;
    ListNode *next;
};

Member functions are defined directly inside the class:

struct Stack {
    
    
    // 成员函数
    void Init(int n = 4) {
    
    
        a = (int *) malloc(sizeof(int) * n);
        if (nullptr == a) {
    
    
            perror("malloc申请空间失败");
            return;
        }

        capacity = n;
        size = 0;
    }

    void Push(int x) {
    
    
        //...
        a[size++] = x;
    }

    int Top(){
    
    
    }

    void Destroy(){
    
    

    }


    // 成员变量
    int *a;
    int size;
    int capacity;
};

int main(){
    
    
    Stack s;
    s.Init(10);
    s.Push(1);
    int x = s.Top();
    s.Destroy();
    return 0;
}

The definition of the above structure is preferred to use class instead in C++ .

3. Class definition

A class definition is used to declare and define a new user-defined data type, which describes a set of data members and member functions.

class ClassName {
    
    
private:
    // 私有成员变量

public:
    // 公有成员变量

    // 构造函数

    // 成员函数
};  // 这里有一个分号

The contents of the class body are called members of the class : variables in the class are called attributes or member variables of the class ; functions in the class are called methods or member functions of the class**.

There are two ways to define a class:

1. The declaration and definition are all placed in the class body. Note: If the member function is defined in the class , the compiler may treat it as an inline function .

class Person {
    
    
private:
    char *_name;
    char *_sex;
    int _age;

public:
    void showInfo() {
    
    
        cout << _name << "-" << _sex << "-" << _age << endl;
    }
};

2. The class declaration is placed in the .h file, and the member function definition is placed in the .cpp file. Note: the class name needs to be added before the member function name::

insert image description here

In general, the second approach is more desirable.

Suggestions for Naming Rules for Member Variables

Let's take a look at this function, isn't it very rigid?

class Date {
    
    
public:
    void Init(int year) {
    
    
        // 这里的year到底是成员变量,还是函数形参?
        year = year;
    }

private:
    int year;
};

So it is generally recommended that:

class Date {
    
    
public:
    void Init(int year) {
    
    
        _year = year;
    }

private:
    int _year;   //成员变量前加上_,用以区分
};

4. Class access qualifiers and encapsulation

The way of C++ to achieve encapsulation: use classes to combine the properties and methods of the object to make the object more complete, and selectively provide its interface to external users through access rights.

4.1 Access Qualifiers

**Access qualifiers: **Access qualifiers are used to control the visibility and access rights of members of a class (member variables and member functions) inside and outside the class. C++ provides three main access qualifiers: public, , privateand protected.

  1. public
    • publicMembers are visible both inside and outside the class.
    • These members can be accessed anywhere, including code outside the class.
    • publicMembers are usually used to represent the interface of a class and are provided for use by external code.
  2. private
    • privateMembers are only visible inside the class and are hidden from outside code.
    • External code cannot directly access or modify these members.
    • privateMembers are often used to encapsulate the implementation details of a class to ensure data security and encapsulation.
  3. protected
    • protectedMembers are visible inside the class and are also visible to derived classes (inherited classes).
    • For code outside the class, protectedmembers privatebehave like members and are not directly accessible.

Protected and private modified members cannot be directly accessed outside the class. What is the difference between them? This involves the concept of inheritance.

class Base {
    
    
private:
    int privateVar;    // 只能在 Base 类内部访问

protected:
    int protectedVar;  // Base 类内部和派生类内部都可访问
};

//Derived类是继承Base类的,继承在后面会介绍
class Derived : public Base {
    
    
public:
    void AccessBaseMembers() {
    
    
        // 可以访问 Base 类的 protected 成员
        protectedVar = 10;
        // 但不能访问 privateVar,因为它是 private 的
    }
};

The Derived class inherits from the Base class, which will be introduced in later chapters

Notice:

Access scopes start at the occurrence of this access qualifier until the next occurrence of the access qualifier

If there is no access qualifier behind, the scope will go to }, which is the end of the class.

The default access permission of class is private, and struct is public (because struct is compatible with C)

4.2 Packaging

The three major characteristics of object-oriented: encapsulation, inheritance, and polymorphism.

In the class and object stage, it is mainly to study the encapsulation characteristics of the class, so what is encapsulation?

Encapsulation is an important concept in object-oriented programming. It refers to combining data (member variables) and methods of manipulating data (member functions) to form a class, and restricting external direct access to data inside the class. The purpose of encapsulation is to hide the internal implementation details of the class, provide certain access control, so as to better manage and maintain the code, and ensure the security and consistency of data.

Encapsulation is essentially a management that makes it easier for users to use classes . For example: for such a complex device as a computer, the only thing provided to the user is the power button, keyboard input, monitor, USB jack, etc., allowing the user to interact with the computer and complete daily affairs. But in fact, the real work of the computer is some hardware components such as CPU, graphics card, and memory.

For computer users, they don’t need to care about internal core components, such as how the circuits on the motherboard are laid out, how the CPU is designed inside, etc. Users only need to know how to turn on the computer and how to interact with the computer through the keyboard and mouse. Therefore, when the computer manufacturer leaves the factory, it puts a shell on the outside to hide the internal implementation details, and only provides external switches, mouse and keyboard jacks, etc., so that users can interact with the computer.

Encapsulation in the C++ language can organically combine data and methods of manipulating data through classes, hide internal implementation details of objects through access rights, and control which methods can be used directly outside the class.

5. Class scope

Class scope refers to the visibility and accessibility of members within the class. The scope of a class is divided into the following parts:

In-class scope :

  • Inside the class, all members (member variables and member functions) can access each other without prefix.
  • Member functions can directly access private members of a class.
class MyClass {
    
    
private:
    int privateVar;

public:
    void publicFunc() {
    
    
        privateVar = 10;  // 直接访问私有成员
    }
};

Out-of-class scope :

  • When accessing a member outside the class, you need to use the member access operator .or ->(in the case of a pointer object), and ::.
  • Only public members and protected members (visible inside derived classes) can be directly accessed outside the class.
MyClass obj;
obj.publicFunc();  // 访问公有成员函数

obj.privateVar = 20;  // 错误,无法直接访问私有成员

Scope of Inherited and Derived Classes :

  • A derived class can access public and protected members of its base class, but not private members of the base class.
  • A derived class can use member access operators to access members inherited from a base class.
class Derived : public MyClass {
    
    
public:
    void derivedFunc() {
    
    
        privateVar = 30;  // 错误,不能访问基类的私有成员

        publicFunc();     // 可以访问继承来的公有成员函数
    }
};

6. Class instantiation

The process of creating an object with a class type is called instantiation of the class ,

  1. A class is a description of an object . It is a model-like thing, which defines which members a class has, and defines a class without allocating actual memory space to store it; Think of it as a class to describe specific student information.
  2. A class can instantiate multiple objects, and the instantiated objects occupy the actual physical space and store class member variables
  3. Instantiating an object from a class is like building a house using architectural design drawings in reality. A class is like a design drawing , only what is needed is designed, but there is no physical building. Objects can actually store data and occupy physical space

Example 1:

#include <iostream>
using namespace std;
class Person {
    
    
public:
    char* _name;
    char* _sex;
    int _age;

public:
    void showInfo() {
    
    
        cout << _name << "-" << _sex << "-" << _age << endl;
    }
};

int main() {
    
    
    //Person::_age = 100;// 编译失败:error C2059
    Person p;   //先实例化对象
    p._age = 100;  //在访问对象成员
    cout << p._age << endl;   //100
    return 0;
}

Example 2:

#include <iostream>
using namespace std;
// 类 -- 别墅设计图
class Date {
    
    
public:
    // 定义
    void Init(int year, int month, int day) {
    
    
        _year = year;
        _month = month;
        _day = day;
    }

private:
    //对数据声明,而不是定义,定义是在创建对象的时候
    int _year;
    int _month;
    int _day;
};

int main() {
    
    
    // 类对象实例化 -- 开空间
    // 实例化 -- 用设计图建造一栋栋别墅
    Date d1;
    Date d2;

    //Date.Init(2023,2,2); //错误,调用成员函数,要用对象,不能用类
    d1.Init(2023, 12, 12);
    d2.Init(2022, 1, 1);
    //d1,d2中的成员变量占用不同的内存空间
    return 0;
}

Why are member variables in the object, but member functions are not in the object?

Each object member variable is different and needs to be stored independently

Calling member functions of each object is the same, placed in the shared public area (code segment)

7. Size and memory alignment of class objects

7.1 Class object size

How to calculate the size of a class object

class A {
    
    
public:
    void PrintA() {
    
    
        cout << _a << endl;
    }

private:
    char _a;
};

**Question:** A class can have both member variables and member functions, so what does an object of a class contain? How to calculate the size of a class?

7.2 Guessing the storage method of class objects

  • The object contains each member of the class
    insert image description here
    Defect: The member variables in each object are different, but the same function is called. If stored in this way, when a class creates multiple objects, each object will save a code , the same code is saved multiple times, wasting space . So how to solve it?

  • Only one copy of the code is saved, and the address where the code is stored is saved in the object
    insert image description here

  • Only member variables are saved, and member functions are stored in public code segments
    insert image description here
    insert image description here

Question: For the above three storage methods, which method does the computer store in?

#include <iostream>
using namespace std;
// 类中既有成员变量,又有成员函数
class A1 {
    
    
public:
    void f1() {
    
    }

private:
    int _a;
};
// 类中仅有成员函数
class A2 {
    
    
public:
    void f2() {
    
    }
};
// 类中什么都没有---空类
class A3 {
    
    };

int main(){
    
    
    cout<<sizeof(A1)<<endl;   //4
    cout<<sizeof(A2)<<endl;   //1
    cout<<sizeof(A3)<<endl;   //1
    return 0;
}

in conclusion:

The size of a class is actually the sum of "member variables" in the class. Of course, pay attention to memory alignment and the size of the empty class. The empty class is special. The compiler gives the empty class a byte to uniquely identify the object of this class.

7.3 Memory alignment rules for classes

The same as the C language structure memory rules, you can access the structure memory alignment rules in the C language self-defined type chapter C language self-defined type

8. this pointer

8.1 Derivation of this pointer

Let's first define a date classDate

#include <iostream>
using namespace std;
class Date {
    
    
public:
    void Init(int year, int month, int day) {
    
    
        _year = year;
        _month = month;
        _day = day;
    }
    void Print() {
    
    
        cout << _year << "-" << _month << "-" << _day << endl;
    }

private:
    int _year; // 年
    int _month;// 月
    int _day;  // 日
};

int main() {
    
    
    Date d1, d2;
    d1.Init(2022, 1, 11);
    d2.Init(2022, 1, 12);
    d1.Print();
    d2.Print();
    return 0;
}

In the two member functions Init and Print in the above Date class, there is no distinction between different objects in the function body, so when d1 calls the Init function, how does the function know that the d1 object should be set instead of the d2 object?

C++ solves this problem by introducing the this pointer, that is: the C++ compiler adds a hidden pointer parameter to each "non-static member function", making the pointer point to the current object (the object that calls the function when the function is running), All operations of "member variables" in the function body are accessed through this pointer. It's just that all operations are transparent to the user, that is, the user does not need to pass it, and the compiler completes it automatically.

8.2 Characteristics of this pointer

1. The type of this pointer: class type const *, that is, in member functions, the this pointer cannot be assigned a value.
2. It can only be used inside the "member function"
3**. This pointer is essentially the formal parameter of the "member function". When the object calls the member function, the object address is passed as the actual parameter to the this formal parameter. So the this pointer is not stored in the object .
4. The this pointer is the first implicit pointer parameter of the "member function" . Generally, it is automatically passed by the compiler through the ecx register and does not need to be passed by the user.

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-ZDTYVzHc-1690953629477) (C:\Users\32784\AppData\Roaming\Typora\typora-user-images\ image-20230802124337941.png)]

Determine whether the following three results are crashes, compilation errors, or normal operations

#include<iostream>
using namespace std;
class Date{
    
    
public:
    // 定义
    void Init(int year, int month, int day){
    
    
        /*_year = year;
        _month = month;
        _day = day;*/
        cout << this << endl;
        this->_year = year;
        this->_month = month;
        this->_day = day;
    }

    void func(){
    
    
        cout << this << endl;
        cout << "func()" << endl;
    }

//private:
    int _year;  // 声明
    int _month;
    int _day;
};

int main(){
    
    
    Date d1;
    Date d2;
    d1.Init(2022, 2, 2);
    d2.Init(2023, 2, 2);
    
    //判断下面三种结果是运行崩溃,还是编译错误,还是正常运行
    Date* ptr = nullptr;
    //ptr为一个对象指针,ptr->表示访问对象成员
    ptr->Init(2022, 2, 2); 
    //运行崩溃,进入Init函数体,this指针为nullptr,this指针访问对象成员变量,出现空指针解引用。

    ptr->func();           
    // 正常运行,func函数中,没有this指针访问成员变量,不会发生空指针解引用
    (*ptr).func();           
    // 正常运行,(*ptr)等价于ptr,

    return 0;
}

9. Comparison between C language and C++ implementation of Stack

C language implementation

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
typedef int DataType;
typedef struct Stack {
    
    
    DataType *array;
    int capacity;
    int size;
} Stack;

void StackInit(Stack *ps) {
    
    
    assert(ps);
    ps->array = (DataType *) malloc(sizeof(DataType) * 3);
    if (NULL == ps->array) {
    
    
        assert(0);
        return;
    }
    ps->capacity = 3;
    ps->size = 0;
}

void StackDestroy(Stack *ps) {
    
    
    assert(ps);
    if (ps->array) {
    
    
        free(ps->array);
        ps->array = NULL;
        ps->capacity = 0;
        ps->size = 0;
    }
}

void CheckCapacity(Stack *ps) {
    
    
    if (ps->size == ps->capacity) {
    
    
        int newcapacity = ps->capacity * 2;
        DataType *temp = (DataType *) realloc(ps->array,
                                              newcapacity * sizeof(DataType));
        if (temp == NULL) {
    
    
            perror("realloc申请空间失败!!!");
            return;
        }
        ps->array = temp;
        ps->capacity = newcapacity;
    }
}

void StackPush(Stack *ps, DataType data) {
    
    
    assert(ps);
    CheckCapacity(ps);
    ps->array[ps->size] = data;
    ps->size++;
}

int StackEmpty(Stack *ps) {
    
    
    assert(ps);
    return 0 == ps->size;
}

void StackPop(Stack *ps) {
    
    
    if (StackEmpty(ps))
        return;
    ps->size--;
}

DataType StackTop(Stack *ps) {
    
    
    assert(!StackEmpty(ps));
    return ps->array[ps->size - 1];
}

int StackSize(Stack *ps) {
    
    
    assert(ps);
    return ps->size;
}

int main() {
    
    
    Stack s;
    StackInit(&s);
    StackPush(&s, 1);
    StackPush(&s, 2);
    StackPush(&s, 3);
    StackPush(&s, 4);
    printf("%d\n", StackTop(&s));
    printf("%d\n", StackSize(&s));
    StackPop(&s);
    StackPop(&s);
    printf("%d\n", StackTop(&s));
    printf("%d\n", StackSize(&s));
    StackDestroy(&s);
    return 0;
}

It can be seen that when implemented in C language, Stack-related operation functions have the following commonality:

  • The first parameter of each function is Stack*
  • The first parameter must be detected in the function, because the parameter may be NULL
  • In the function, the stack is manipulated through the Stack* parameter
  • The address of the Stack structure variable must be passed when calling

Only the structure for storing data can be defined in the structure, and the method of operating data cannot be placed in the structure, that is, the data and the way of operating data are separated, and the implementation is quite complicated, involving a large number of pointer operations, so pay attention It might go wrong.

C++ implementation

#include <iostream>
#include <stdlib.h>
using namespace std;
typedef int DataType;
class Stack {
    
    
public:
    void Init() {
    
    
        _array = (DataType *) malloc(sizeof(DataType) * 3);
        if (NULL == _array) {
    
    
            perror("malloc申请空间失败!!!");
            return;
        }
        _capacity = 3;
        _size = 0;
    }

    void Push(DataType data) {
    
    
        CheckCapacity();
        _array[_size] = data;
        _size++;
    }

    void Pop() {
    
    
        if (Empty())
            return;
        _size--;
    }

    DataType Top() {
    
    
        return _array[_size - 1];
    }

    int Empty() {
    
    
        return 0 == _size;
    }

    int Size() {
    
    
        return _size;
    }

    void Destroy() {
    
    
        if (_array) {
    
    
            free(_array);
            _array = NULL;
            _capacity = 0;
            _size = 0;
        }
    }

private:
    void CheckCapacity() {
    
    
        if (_size == _capacity) {
    
    
            int newcapacity = _capacity * 2;
            DataType *temp = (DataType *) realloc(_array, newcapacity *
                                                                  sizeof(DataType));
            if (temp == NULL) {
    
    
                perror("realloc申请空间失败!!!");
                return;
            }
            _array = temp;
            _capacity = newcapacity;
        }
    }

private:
    DataType *_array;
    int _capacity;
    int _size;
};

int main() {
    
    
    Stack s;
    s.Init();
    s.Push(1);
    s.Push(2);
    s.Push(3);
    s.Push(4);

    printf("%d\n", s.Top());
    printf("%d\n", s.Size());
    s.Pop();
    s.Pop();
    printf("%d\n", s.Top());
    printf("%d\n", s.Size());
    s.Destroy();
    return 0;
}

In C++, the data and the methods of manipulating the data can be perfectly combined through the class, and those methods that can be called outside the class can be controlled through the access authority, that is, encapsulation . cognition of things. Moreover, each method does not need to pass the Stack* parameter, and the parameter will be automatically restored after the compiler compiles, that is, the Stack * parameter in C++ is maintained by the compiler, but in C language, it needs to be maintained by the user .

Guess you like

Origin blog.csdn.net/ikun66666/article/details/132059987