C++ 面向对象程序设计 14万字总结笔记(三)

简介: C++ 面向对象程序设计 14万字总结笔记(三)

//复制构造函数 copy constructor

基本概念

只有一个参数,即对同类对象的引用。

形如 X::X( X& )或X::X(const X &), 二者选一后者能以常量对象作为参数

如果没有定义复制构造函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能。

复制构造函数是一个特殊的构造函数,用于创建一个新对象,该对象与另一个已经存在的同类对象具有相同的值。通常在需要创建一个与已有对象值相同的新对象时使用。下面是一个C++语言的示例代码,其中定义了一个名为Person的类,包含成员变量name和age,并实现了一个复制构造函数。

#include <iostream>
#include <string>
using namespace std;
class Person {
public:
    string name;
    int age;
    Person(string n, int a) : name(n), age(a) {} // 构造函数
    Person(const Person &p) {                  // 复制构造函数
        name = p.name;
        age = p.age;
    }
};
int main() {
    Person p1("Alice", 20);
    Person p2 = p1;     // 使用复制构造函数创建p2对象,其值与p1相等
    cout << p1.name << " " << p1.age << endl;
    cout << p2.name << " " << p2.age << endl;
    return 0;
}

在上述示例代码中,我们定义了一个Person类,它包含两个成员变量:name和age。在构造函数中初始化这两个成员变量。另外,还实现了一个复制构造函数,在创建新的Person类对象时,将原对象的属性进行拷贝并赋给新对象,从而使新对象与原对象的值相等。

在主函数中,首先创建了一个Person类型的对象“p1”,然后通过使用复制构造函数创建了一个新对象“p2”。最后,分别输出两个对象的姓名和年龄。可以看到,p1和p2的值是相等的。

总之,复制构造函数在C++语言中是一个很常用的函数,通过它我们可以将已有对象的值复制并赋给一个新的对象。

复制构造函数有以下几个特点:

  1. 它是一个特殊的构造函数,带有唯一的参数,即对同类的引用。
  2. 当使用同类的对象来初始化一个新的对象时,复制构造函数会被自动调用。例如,如上述示例中的语句“Person p2 = p1;”。
  3. 复制构造函数的作用是实现对象的浅拷贝或深拷贝。在进行复制操作时,可以根据需要选择对成员变量进行赋值,从而使得新对象与原对象具有相同的值。
  4. 如果没有显式地定义复制构造函数,编译器也会自动生成一个默认的复制构造函数,但该函数只是简单地将原始对象的所有成员变量复制到新对象中,不能满足所有情况的需求,因此通常我们需要根据特定场景自己定义复制构造函数。

需要注意的是,由于复制构造函数经常用到,所以如果程序中存在指向动态内存的指针或者引用,那么在定义复制构造函数时要非常小心。复制构造函数中的逻辑运算应该保证复制后的对象和原来的对象彼此独立,而且同样避免出现野指针等错误。

通常情况下,当类中包含了动态分配的内存时,需要重载复制构造函数以实现正确的深度复制。下面给出一个示例代码:

class Person {
public:
    string *name;          // 定义指向string类型的指针
    int age;
    Person(string n, int a) : age(a) {       // 构造函数
        name = new string(n);                // 动态分配内存并初始化
    }
    Person(const Person &p) : age(p.age) {  // 复制构造函数
        name = new string(*(p.name));        // 深拷贝指向string的指针
    }
    ~Person() {                             // 析构函数
        delete name;                        // 释放动态分配的内存
    }
};

在上述示例中,我们更改了Person类的实现方式,将其中的name变量从string类型变为了指向string类型的指针,并重载了复制构造函数与析构函数。

在构造函数中,我们使用了动态内存分配语句new string(n),这意味着我们为name变量分配了一块新的内存,并将它的指针赋值给了name。同时,在析构函数中通过调用delete name来释放动态分配的内存。

在复制构造函数中,我们为新对象分配了一块新的内存,并将原对象指针所指向的内容复制到新的内存中。这样,我们实现了对原对象和新对象内存空间的分离管理。

总之,正确地重载复制构造函数能够避免由于复制操作引起的一些问题,如野指针、内存泄漏等等。因此,在定义类的时候,如果有必要使用动态内存,就必须同时重新制定析构函数与复制构造函数来进行动态内存管理。

复制构造函数一般用于以下场景:

  1. 对象的初始化:在创建对象时,可以通过拷贝已有对象来初始化新的对象。此时,编译器会自动调用复制构造函数。
  2. 对象的赋值:当把一个对象赋值给另一个对象时,也会调用复制构造函数。例如,Person p1; Person p2 = p1;。这样就会使用p1对象的值进行初始化p2对象。
  3. 以值传递方式传递对象:当函数参数是一个对象时,如果该参数被以值传递的方式传递,那么将会调用其复制构造函数。例如,在定义函数void printPerson(Person p);时,如果调用printPerson(p1),那么它就会调用复制构造函数进行参数传递。

需要注意的是,在上述三种情况下,会自动调用复制构造函数,从而创建一个与原始对象相同的新对象。但在实际业务中,由于各种原因,程序员有时也要手动调用复制构造函数,来产生一个新对象并初始化它的内容。

//常量引用参数的使用

void fun(CMyclass obj_ ) {
cout << "fun" << endl;
}
这样的函数,调用时生成形参会引发复制构造函数调用,开销比较大。
所以可以考虑使用 CMyclass & 引用类型作为参数。
如果希望确保实参的值在函数中不应被改变,那么可以加上const 关键字:
void fun(const CMyclass & obj) {
//函数中任何试图改变 obj值的语句都将是变成非法
}

类型转换构造函数

定义转换构造函数的目的是实现类型的自动转换。

只有一个参数,而且不是复制构造函数的构造函数,一般就可以看作是转换构造函数。

当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)。

类型转换构造函数是一种将一个对象从一种类型转换为另一种类型的方式。

在C++中,我们可以在类中定义类型转换构造函数,来实现将类的对象隐式地转换成其他类型。其中,类型转换构造函数也是一种特殊的构造函数,它允许直接将一种类型的对象转换为另一种类型的对象。

下面给出一个简单的示例:

#include<iostream>
using namespace std;
class Complex{
    private:
        double real,imag;
    public:
        Complex(int a):real(a), imag(0){    // 定义类型转换构造函数
            cout << "Converted!" << endl;
        }
};
int main(){
    int num = 3;
    Complex c1 = num;   // 将整型数num转换为Complex类型,此处会自动调用类型转换构造函数
    return 0;
}

在上述代码中,我们定义了一个名为Complex的类,包含两个成员变量realimag。在这个类的定义中,我们定义了一个类型转换构造函数,该构造函数只有一个参数,即一个整型数a,它用于把整数转换成复数。

在主函数中,我们定义了一个整数类型的变量num,并使用Complex c1 = num;语句将它隐式转换为了Complex类型的对象。运行程序后,可以看到输出信息“Converted!”,表示成功调用了类型转化构造函数。

总之,类型转换构造函数是一种可用于将一种类的对象隐式转化为另一种类型的构造函数。虽然可以很方便地将它们用作数据类型转换,但需要注意的是,它可能会导致程序出现潜在的问题(如精度误差等)。因此,在使用时必须小心谨慎。

//析构函数destructors

名字与类名相同,在前面加‘~’, 没有参数和返回值,一个类最多只能有一个析构函数。

析构函数对象消亡时即自动被调用。可以定义析构函数来在对象消亡前做善后工作,比如释放分配的空间等。

如果定义类时没写析构函数,则编译器生成缺省析构函数。缺省析构函数什么也不做。

如果定义了析构函数,则编译器不生成缺省析构函数。

析构函数是与构造函数相对应的一种特殊函数,主要用于在对象生命周期结束时清理对象占用的内存资源。

在C++中,每个类都可以定义自己的析构函数,它由一个波浪号~和类名组成,并且不需要参数或返回值。通常情况下,需要清理动态分配的内存、关闭打开的文件、释放网络连接等等操作都可以在析构函数中实现。

在下面的示例代码中,我们定义了一个名为Person的类,其中包含有两个成员变量nameage,我们在这个类中定义了一个简单的析构函数,用于在对象被销毁时输出一条消息:

#include<iostream>
using namespace std;
class Person {
public:
    string name;
    int age;
    Person(string n, int a): name(n), age(a) { // 构造函数
        cout << "Object created." << endl;
    }
    ~Person(){                                 // 析构函数
        cout << "Object destroyed." << endl;
    }
};
int main() {
    Person p1("张三", 20);
    return 0;
}

在上述代码中,我们定义了一个Person对象p1,并初始化了它的属性(姓名和年龄)。在main()函数结束后,程序运行完毕,也就是Person对象p1的生命周期结束时,会自动调用析构函数~Person()来释放占用的内存资源。在这个析构函数中,我们简单地输出了一条消息“Object destroyed.”。

总之,析构函数是类的一个特殊函数,在对象生命周期结束时自动调用(或者手动调用),它的主要作用就是清理对象所占用的资源,例如动态分配的内存、打开的文件等。借助析构函数,我们能够更好地控制程序使用内存资源的方式,并避免出现内存泄漏等问题。

为什么需要析构函数

在C++中,我们经常要使用动态内存分配来创建对象,例如使用new关键字来为一个对象分配内存空间。由于程序运行完毕后需要释放这部分动态分配的内存空间,因此我们需要使用delete关键字来释放这些内存空间。

如果我们没有合理地释放这些内存,在程序执行一段时间之后,就会耗尽机器物理内存,导致系统崩溃或程序运行效率下降。因此,为了避免出现内存泄漏等问题,我们可以使用析构函数来释放所占用的内存资源。

此外,析构函数的另一个重要作用是帮助我们管理类中的各种数据结构和状态。通常我们可以使用析构函数来释放资源但需要注意的是,在大多数情况下,析构函数在对象被销毁时自动调用,程序员无需手动调用。

综上所述,析构函数是为了解决程序中动态内存申请和管理的问题而存在的,它是一种特殊的函数,当对象生命周期结束时自动调用,主要用于清理对象所占用的资源、还原对象的状态等。使用析构函数能够有效地管理内存资源,防止内存泄漏以及其它相关问题的发生。

析构函数和数组

在C++中,数组的生命周期也和其他对象一样;当程序执行到数组的作用域结束时,或者使用delete运算符来释放动态分配的数组空间时,数组也要被销毁。因此,如果一个类定义了析构函数,则它可以管理动态数组,并在销毁对象时自动调用。

下面是一个简单的示例代码,展示了如何在类中使用析构函数来管理动态数组:

#include<iostream>
using namespace std;
class Array{
private:
    int *arr;
    int size;
public:
    Array(int s):size(s) {          // 构造函数,创建一个大小为s的数组
        arr = new int[s];
    }
    ~Array() {                      // 析构函数,释放数组所占用的内存
        delete[] arr;
    }
};
int main(){
    Array a1(10);                  // 创建大小为10的数组对象
    return 0;
}

在上述代码中,我们定义了一个名为Array的类,其中包含一个指向int类型的指针arr,用于动态分配内存,并且定义了一个整型变量用于记录数组的大小。在类的构造函数Array(int s)中,我们使用new运算符为这个数组分配了s个元素的内存,并保存指向其地址的指针。然后,在析构函数~Array()中,我们使用delete[]运算符来释放数组的内存空间。

main()函数中,我们创建一个大小为10的数组对象a1,用于展示如何在类中使用析构函数来管理动态数组。当程序执行结束时,自动调用Array类的析构函数,进而自动释放动态分配的内存空间。

值得注意的是,在使用析构函数管理动态数组时需要小心一些细节问题,下面列出几点需要特别注意的事项:

  1. 析构函数应该使用delete[]运算符释放数组所占用的内存空间,否则可能会导致内存泄漏或程序崩溃。
  2. 如果在类中定义了其他指针类型的成员变量,例如char*型指针,则需要编写相应的析构函数来释放这些指针所指向的内存空间。
  3. 在使用动态数组时,应该进行异常处理,防止因为内存分配失败导致内存泄漏或程序崩溃。
  4. 除非有必要,否则不应该将动态数组作为类成员来使用,否则可能会破坏类的封装性和可移植性。如果必须使用动态数组作为类成员变量,则应该使用复制构造函数和赋值运算符来处理对象之间的拷贝和赋值操作。

总之,使用析构函数可以有效地管理动态数组并防止内存泄漏等问题,但需要程序员对其进行正确的使用与实现。

析构函数和运算符 delete

delete 运算导致析构函数调用。

Ctest * pTest;
pTest = new Ctest; //构造函数调用
pTest = new Ctest[3]; //构造函数调用3次
delete [] pTest; //析构函数调用3次

若new一个对象数组,那么用delete释放时应该写 []。否则只delete一个对象(调用一次析构函数)

析构函数在对象作为函数返回值返回后被调用

class CMyclass {
public:
~CMyclass() { cout << "destructor" << endl; }
};
CMyclass obj;
CMyclass fun(CMyclass sobj ) { //参数对象消亡也会导致析
//构函数被调用
return sobj; //函数调用返回时生成临时对象返回
}
int main(){
obj = fun(obj); //函数调用的返回值(临时对象)被
return 0; //用过后,该临时对象析构函数被调用
}

输出:

destructor

destructor

destructor

构造函数和析构函数什么时候被调用

构造函数和析构函数的调用时机如下:

构造函数:

  • 在创建对象时自动被调用,即当我们定义一个类的对象,使用它的构造函数来初始化该对象时就会被自动调用。
  • 构造函数还可以在派生类中显示调用基类的构造函数。这通常是通过在子类的成员初始化列表中调用它来实现的。

析构函数:

  • 当一个对象生命到达其范围的尽头或者当对动态分配内存空间的对象应用delete运算符时,析构函数将被自动调用。
  • 在一些情况下,析构函数也可以显式地调用来释放对象的资源。

例如,考虑下面这个小例子:

#include<iostream>
using namespace std;
class Person {
public:
    string name;
    int age;
    Person() {
        cout << "Constructor called!" << endl;
    }
    ~Person() {
        cout << "Destructor called!" << endl;
    }
};
int main() {
    Person p1;               // 创建Person类型的对象p1,此时构造函数被调用
    return 0;
}   

在上述代码中,我们定义了一个名为Person的类,并在其中定义了构造函数和析构函数。在main()函数中,我们创建一个Person类型的对象p1,它的定义触发了构造函数的调用;在程序结束之前,Person对象p1的范围已经结束,同时达到了它的生命周期结束这一条件,触发了析构函数的调用。

总之,构造函数和析构函数都将在对象创建和销毁时分别被自动调用。对于不同类型的类,当创建或销毁实例时,C++编译器会自动调用相应的构造函数和析构函数来处理相关的操作。

需要注意的是,如果一个类包含静态成员变量,则必须显式地定义该静态成员变量的构造函数和析构函数。因为静态对象必须在程序开始执行之前被初始化,在程序结束时被销毁。例如:

#include<iostream>
using namespace std;
class MyClass {
public:
    MyClass() {
        cout << "Constructor called." << endl;
    }
    ~MyClass() {
        cout << "Destructor called." << endl;
    }
};
class WithStaticMember{
public:
    static MyClass sm;    // 静态成员变量,必须显式定义其构造函数和析构函数
    WithStaticMember() {
        cout << "WithStaticMember constructor called." << endl;
    }
    ~WithStaticMember() {
        cout << "WithStaticMember destructor called." << endl;
    }
};
MyClass WithStaticMember::sm;  // 显式定义静态成员变量的构造函数和析构函数
int main() {
    WithStaticMember wsm;      // 创建对象wsm,此时调用相关构造函数
    return 0;
}

在上述代码中,我们定义了一个名为WithStaticMember的类,并在其中定义了包含一个静态成员变量sm的类成员。由于静态成员变量必须在类外部进行初始化,所以我们需要在程序文件级别上定义其构造函数和析构函数。在main()函数中,我们创建WithStaticMember类的实例wsm,此时相关构造函数自动调用;当程序结束时,对应的析构函数被自动调用。

总之,在包含静态成员变量的类中,必须显式定义其构造函数和析构函数以正确地初始化和销毁这些变量,否则会导致程序无法编译或运行异常。

//C++程序到C程序的翻译

class CCar {
public:
int price;
void SetPrice(int p);
};
void CCar::SetPrice(int p) 
{ price = p; }
int main()
{
CCar car;
car.SetPrice(20000);
return 0;
}
struct CCar { 
int price; 
};
void SetPrice(struct CCar * this,
int p)
{ this->price = p; }
int main() {
struct CCar car;
SetPrice( & car, 
20000);
return 0;
}

this指针作用

其作用就是指向成员函数所作用的对象

非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针。

class Complex {
public:
double real, imag;
void Print() { cout << real << "," << imag ; }
Complex(double r,double i):real(r),imag(i) 
{ }
Complex AddOne() {
this->real ++; //等价于 real ++;
this->Print(); //等价于 Print
return * this;
} 
}; 
int main() {
Complex c1(1,1),c2(0,0);
c2 = c1.AddOne();
return 0;
} //输出 2,1
class A
{ 
int i;
public: 
void Hello() { cout << i << "hello" << endl; } 
};  void Hello(A * this ) { cout << this->i << "hello" 
<< endl; } 
//this若为NULL,则出错!!
int main() 
{ 
A * p = NULL;
p->Hello();  Hello(p);
} // 输出:hello

在C++中,this指针是一个特殊的指针,它指向当前对象的地址。它可以在类的成员函数中使用,用于访问调用该函数的对象的成员变量和成员函数。

当类的成员函数被调用时,编译器会隐式地传递一个this指针作为参数给该函数,以便让函数知道它是哪个对象的成员函数被调用了。通过this指针,我们可以在成员函数中访问对象的成员变量和其他成员函数。

举个例子,假设有一个名为"Person"的类,其中有一个成员变量name和一个成员函数printName(),那么在printName()函数中,可以使用this指针来访问name成员变量,如下所示:

class Person {
private:
    std::string name;
public:
    void setName(std::string n) {
        this->name = n;
    }
    void printName() {
        std::cout << "My name is: " << this->name << std::endl;
    }
};

在上面的例子中,this->name表示当前对象的name成员变量,this->name = n表示将传入的n赋值给当前对象的name成员变量。

需要注意的是,this指针只能在非静态成员函数中使用。静态成员函数没有this指针,因为它们不属于任何具体的对象。

总结一下,this指针在C++中用于访问当前对象的成员变量和成员函数,它是一个隐含传递给成员函数的指针。通过this指针,可以方便地操作和使用对象的成员。

在C++中,this指针是一个特殊的指针,它指向当前对象的地址。它可以在类的成员函数中使用,用于访问调用该函数的对象的成员变量和成员函数。

当类的成员函数被调用时,编译器会隐式地传递一个this指针作为参数给该函数,以便让函数知道它是哪个对象的成员函数被调用了。通过this指针,我们可以在成员函数中访问对象的成员变量和其他成员函数。

举个例子,假设有一个名为"Person"的类,其中有一个成员变量name和一个成员函数printName(),那么在printName()函数中,可以使用this指针来访问name成员变量,如下所示:

class Person {
private:
    std::string name;
public:
    void setName(std::string n) {
        this->name = n;
    }
    void printName() {
        std::cout << "My name is: " << this->name << std::endl;
    }
};

在上面的例子中,this->name表示当前对象的name成员变量,this->name = n表示将传入的n赋值给当前对象的name成员变量。

需要注意的是,this指针只能在非静态成员函数中使用。静态成员函数没有this指针,因为它们不属于任何具体的对象。

当涉及到多个对象之间的交互时,this指针也非常有用。通过使用this指针,我们可以将一个对象作为参数传递给另一个对象的成员函数,从而实现对象之间的通信。

举个例子,假设我们有两个类:Student和Teacher。在Teacher类中,我们定义了一个成员函数addStudent(),用于向Teacher对象中添加一个Student对象。代码如下所示:

class Student {
private:
    std::string name;
public:
    Student(std::string n) : name(n) {}
    std::string getName() {
        return name;
    }
};
class Teacher {
private:
    std::vector<Student> students;
public:
    void addStudent(Student& student) {
        students.push_back(student);
        std::cout << "Added student: " << student.getName() << std::endl;
    }
};

在上面的例子中,addStudent()函数接受一个Student对象的引用作为参数。通过使用this指针,我们可以在Teacher对象中调用该函数,并将当前Teacher对象作为参数传递给addStudent()函数,从而将自己添加到Teacher对象的students容器中。

使用示例如下:

int main() {
    Teacher teacher;
    Student student1("Alice");
    teacher.addStudent(student1);
    Student student2("Bob");
    teacher.addStudent(student2);
    return 0;
}

在上述示例中,我们创建了一个Teacher对象teacher,并创建了两个Student对象student1和student2。然后,我们通过调用teacher的addStudent()函数,将student1和student2添加到teacher对象的students容器中。

这个例子展示了如何使用this指针在对象之间传递信息,实现对象之间的交互。通过this指针,我们可以方便地操作其他对象的成员函数和成员变量。

this指针和静态成员函数

静态成员函数中不能使用 this 指针!

因为静态成员函数并不具体作用与某个对象!

因此,静态成员函数的真实的参数的个数,就是程序中写出的参数个数!

在C++中,this指针和静态成员函数之间有一些区别。

  1. 静态成员函数没有this指针:由于静态成员函数不属于任何特定的对象,它们没有this指针。因此,在静态成员函数中不能使用this指针来访问对象的成员变量或其他非静态成员函数。
  2. 静态成员函数可以直接访问静态成员:静态成员函数只能访问静态成员变量和其他静态成员函数。静态成员是与类关联而不是与对象关联的,所以无需实例化对象即可访问。这是因为静态成员在编译时已经被分配了内存空间。
  3. 非静态成员函数可以访问this指针:非静态成员函数可以使用this指针来访问调用该函数的对象的成员变量和其他非静态成员函数。this指针是一个隐式传递给非静态成员函数的指针,它指向当前对象的地址。

需要注意的是,静态成员函数可以通过类名直接调用,而非静态成员函数必须通过对象来调用。另外,静态成员函数在全局作用域中也是可见的,可以通过类名来访问,而非静态成员函数则需要通过对象或对象指针来访问。

举个例子,假设有一个名为"Math"的类,其中包含一个静态成员变量PI和一个静态成员函数add(),以及一个非静态成员变量num和一个非静态成员函数multiply()。代码如下所示:

class Math {
public:
    static const double PI;
    static int add(int a, int b) {
        return a + b;
    }
    int num;
    int multiply(int a, int b) {
        return a * b;
    }
};
const double Math::PI = 3.14159;

在上述示例中,静态成员变量PI可以直接通过类名访问,如Math::PI。静态成员函数add()也可以通过类名直接调用,如int sum = Math::add(2, 3)

而非静态成员变量num和非静态成员函数multiply()需要通过对象来访问,如:

Math math;
math.num = 10;
int product = math.multiply(4, 5);

总结一下,this指针只能在非静态成员函数中使用,用于访问当前对象的成员变量和其他非静态成员函数。静态成员函数没有this指针,只能访问静态成员变量和其他静态成员函数。静态成员通过类名直接访问,而非静态成员需要通过对象来访问。

在C++中,静态成员是属于类本身而不是类的实例的成员。静态成员在所有类的对象之间是共享的,它们被存储在静态数据区中,并且在程序运行期间只有一份副本。

静态成员可以是静态成员变量或静态成员函数。

  1. 静态成员变量:静态成员变量是与类关联而不是与类的对象关联的。它们由类的所有对象共享,无论创建多少个对象,静态成员变量都只有一个副本。静态成员变量通常用于表示与类相关的全局属性或计数器等。定义静态成员变量时,需要在声明前加上static关键字,并在类外部初始化。
class MyClass {
public:
    static int count;
};
int MyClass::count = 0; // 静态成员变量的初始化
int main() {
    MyClass::count++; // 访问静态成员变量使用类名和作用域解析运算符
    return 0;
}
  1. 静态成员函数:静态成员函数是与类关联而不是与类的对象关联的函数。它们不操作特定对象的成员变量,也没有this指针。静态成员函数可以直接通过类名调用,无需实例化对象。静态成员函数通常用于执行与类相关的一般操作,而不依赖于特定对象。
class MyClass {
public:
    static void printMessage() {
        std::cout << "This is a static member function." << std::endl;
    }
};
int main() {
    MyClass::printMessage(); // 调用静态成员函数使用类名和作用域解析运算符
    return 0;
}

需要注意的是,静态成员变量和静态成员函数都属于类本身,而不是类的对象。因此,在静态成员函数中不能直接访问非静态成员变量或非静态成员函数,只能访问其他静态成员。

总结一下,静态成员是属于类本身的成员,与类的对象无关。静态成员变量由类的所有对象共享,并且在程序运行期间只有一个副本。静态成员函数不依赖于特定对象,可以通过类名直接调用。静态成员通常用于表示与类相关的全局属性或执行与类相关的一般操作。

静态成员

普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享。

普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象。

因此静态成员不需要通过对象就能访问。

在C++中,静态成员是属于类本身而不是类的实例的成员。静态成员在所有类的对象之间是共享的,它们被存储在静态数据区中,并且在程序运行期间只有一份副本。

静态成员可以是静态成员变量或静态成员函数。

  1. 静态成员变量:静态成员变量是与类关联而不是与类的对象关联的。它们由类的所有对象共享,无论创建多少个对象,静态成员变量都只有一个副本。静态成员变量通常用于表示与类相关的全局属性或计数器等。定义静态成员变量时,需要在声明前加上static关键字,并在类外部初始化。
class MyClass {
public:
    static int count;
};
int MyClass::count = 0; // 静态成员变量的初始化
int main() {
    MyClass::count++; // 访问静态成员变量使用类名和作用域解析运算符
    return 0;
}
  1. 静态成员函数:静态成员函数是与类关联而不是与类的对象关联的函数。它们不操作特定对象的成员变量,也没有this指针。静态成员函数可以直接通过类名调用,无需实例化对象。静态成员函数通常用于执行与类相关的一般操作,而不依赖于特定对象。
class MyClass {
public:
    static void printMessage() {
        std::cout << "This is a static member function." << std::endl;
    }
};
int main() {
    MyClass::printMessage(); // 调用静态成员函数使用类名和作用域解析运算符
    return 0;
}

需要注意的是,静态成员变量和静态成员函数都属于类本身,而不是类的对象。因此,在静态成员函数中不能直接访问非静态成员变量或非静态成员函数,只能访问其他静态成员。

总结一下,静态成员是属于类本身的成员,与类的对象无关。静态成员变量由类的所有对象共享,并且在程序运行期间只有一个副本。静态成员函数不依赖于特定对象,可以通过类名直接调用。静态成员通常用于表示与类相关的全局属性或执行与类相关的一般操作。

当使用静态成员时,有一些特性和注意事项需要了解:

  1. 静态成员变量的初始化:静态成员变量在类外部进行初始化,通常在源文件中进行。可以通过在类定义之外使用类名和作用域解析运算符来访问和初始化静态成员变量。
  2. 静态成员函数的调用:静态成员函数可以直接通过类名和作用域解析运算符来调用,无需实例化对象。它们不操作特定对象的成员变量,也没有this指针。因此,在静态成员函数内部不能直接访问非静态成员变量或非静态成员函数。
  3. 访问权限:静态成员可以具有公共、私有或受保护的访问权限修饰符,就像其他类成员一样。可以使用public、private或protected关键字来控制对静态成员的访问权限。
  4. 静态成员的作用域和生存周期:静态成员在类的整个生命周期都存在,并且在所有类的对象之间是共享的。静态成员的作用域限于其定义所在的类,可以通过类名和作用域解析运算符来访问。
  5. 静态成员的存储位置:静态成员变量在程序运行期间只有一个副本,它们被存储在静态数据区中。因此,无论创建多少个类的对象,静态成员变量始终保持相同的值。
  6. 静态成员和非静态成员之间的区别:静态成员属于类本身,而非静态成员属于类的对象。静态成员在所有对象之间共享,而非静态成员每个对象都有自己的副本。此外,静态成员函数没有this指针,无法访问非静态成员。

需要注意的是,静态成员的设计应该遵循一定的规则和原则。静态成员通常用于表示与类相关的全局属性或执行与类相关的一般操作,而不依赖于特定对象的状态。在使用静态成员时,应该明确其适用范围和目的,以保证代码的可读性和可维护性。

总结一下,静态成员是属于类本身的成员,与类的对象无关。静态成员变量在程序运行期间只有一个副本,静态成员函数可以直接通过类名调用。静态成员通常用于表示与类相关的全局属性或执行与类相关的一般操作。在使用静态成员时,应注意初始化、访问权限、作用域、生存周期等特性。

如何访问静态成员

如何访问静态成员

1) 类名::成员名
CRectangle::PrintTotal();
1) 对象名.成员名
CRectangle r; r.PrintTotal();
1) 指针->成员名
CRectangle * p = &r; p->PrintTotal();
1) 引用.成员名
CRectangle & ref = r; int n = ref.nTotalNumber; 

要访问静态成员变量和静态成员函数,可以使用类名和作用域解析运算符来进行访问。

  1. 访问静态成员变量:使用类名和作用域解析运算符::来访问静态成员变量。例如,如果有一个名为MyClass的类,并且该类包含一个静态成员变量staticVar,可以通过MyClass::staticVar来访问它。
class MyClass {
public:
    static int staticVar;
};
int MyClass::staticVar = 10; // 初始化静态成员变量
int main() {
    int value = MyClass::staticVar; // 访问静态成员变量
    return 0;
}
  1. 调用静态成员函数:同样地,使用类名和作用域解析运算符::来调用静态成员函数。例如,如果有一个名为MyClass的类,并且该类包含一个静态成员函数staticFunc,可以通过MyClass::staticFunc()来调用它。
class MyClass {
public:
    static void staticFunc() {
        // 静态成员函数的实现
    }
};
int main() {
    MyClass::staticFunc(); // 调用静态成员函数
    return 0;
}

需要注意的是,静态成员变量和静态成员函数在编译期间就已经存在,无需实例化对象即可访问和调用。它们不依赖于特定对象的状态,因此可以直接通过类名进行访问。

总结一下,要访问静态成员变量和调用静态成员函数,使用类名和作用域解析运算符::来进行访问和调用。静态成员变量和静态成员函数在编译期间就已经存在,并且无需实例化对象即可访问和调用。

除了使用类名和作用域解析运算符来访问静态成员,还有其他几种方式可以进行访问。

  1. 通过对象访问静态成员:虽然静态成员是与类关联而不是与对象关联的,但实际上也可以通过对象来访问静态成员。这是因为在编译器中,对象会自动转换为对应的类类型指针,从而可以使用指针来访问静态成员。
class MyClass {
public:
    static int staticVar;
};
int MyClass::staticVar = 10; // 初始化静态成员变量
int main() {
    MyClass obj;
    int value = obj.staticVar; // 通过对象访问静态成员变量
    return 0;
}

尽管可以通过对象访问静态成员,但这样做并不推荐,因为它可能会造成混淆,并且容易让人误以为静态成员是与对象相关联的。

2. 通过指针访问静态成员:与使用对象类似,可以使用指向类类型的指针来访问静态成员。这里需要注意的是,由于静态成员不依赖于特定对象的状态,可以直接通过类名来访问,无需通过指针间接访问。

class MyClass {
public:
    static int staticVar;
};
int MyClass::staticVar = 10; // 初始化静态成员变量
int main() {
    MyClass* ptr = nullptr;
    int value = ptr->staticVar; // 通过指针访问静态成员变量
    return 0;
}

需要注意的是,在使用对象或指针访问静态成员时,静态成员的访问权限仍然适用。如果静态成员是私有的,则无法通过对象或指针进行访问。

除了使用类名和作用域解析运算符来访问静态成员外,还可以通过对象或指针来访问。但这些方式并不推荐,因为静态成员是与类关联而不是与对象关联的。最佳实践是直接使用类名来访问静态成员,以确保清晰明确。

静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在。

静态成员函数本质上是全局函数。

设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解。

注意事项

在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。

void CRectangle::PrintTotal()
{
cout << w << "," << nTotalNumber << "," << 
nTotalArea << endl; //wrong
}

示例:

确实,你可以使用静态成员变量和静态成员函数来封装总数和总面积,并确保在整个程序中只有一个副本。

下面是一个示例代码:

class Rectangle {
private:
    int width;
    int height;
    static int totalCount;
    static int totalArea;
public:
    Rectangle(int w, int h) : width(w), height(h) {
        totalCount++;
        totalArea += width * height;
    }
    static int getTotalCount() {
        return totalCount;
    }
    static int getTotalArea() {
        return totalArea;
    }
};
int Rectangle::totalCount = 0;
int Rectangle::totalArea = 0;
int main() {
    Rectangle rect1(10, 20);
    Rectangle rect2(5, 15);
    int count = Rectangle::getTotalCount();
    int area = Rectangle::getTotalArea();
    return 0;
}

在上述示例中,我们定义了一个Rectangle类,其中包含私有的width和height成员变量,以及静态的totalCount和totalArea成员变量。构造函数会在创建每个矩形对象时自动增加totalCount,并将面积累加到totalArea中。通过静态成员函数getTotalCount()和getTotalArea(),我们可以获取整个程序中所有矩形的总数和总面积。

使用静态成员封装这些变量的好处是,它们与类相关联,并且在整个程序中只有一个副本。这样可以更容易地理解和维护代码,确保数据的一致性。

需要注意的是,静态成员变量和静态成员函数可以在类的定义中声明,在类外部初始化。并且它们不依赖于特定对象的状态,因此可以直接通过类名来访问。

总结一下,使用静态成员变量和静态成员函数可以封装总数和总面积,并确保在整个程序中只有一个副本。这样可以更容易地理解和维护代码,同时保持数据的一致性。

目录
相关文章
|
2月前
|
Java 编译器 C++
C++入门指南:类和对象总结笔记(下)
C++入门指南:类和对象总结笔记(下)
30 0
|
2月前
|
存储 编译器 C语言
C++入门: 类和对象笔记总结(上)
C++入门: 类和对象笔记总结(上)
35 0
|
2月前
|
存储 编译器 C++
C++:多态究竟是什么?为何能成为面向对象的重要手段之一?
C++:多态究竟是什么?为何能成为面向对象的重要手段之一?
52 0
|
1月前
|
算法 Java 程序员
【C++专栏】C++入门 | 类和对象 | 面向过程与面向对象的初步认识
【C++专栏】C++入门 | 类和对象 | 面向过程与面向对象的初步认识
23 0
|
15天前
|
安全 Java 程序员
【C++笔记】从零开始认识继承
在编程中,继承是C++的核心特性,它允许类复用和扩展已有功能。继承自一个基类的派生类可以拥有基类的属性和方法,同时添加自己的特性。继承的起源是为了解决代码重复,提高模块化和可维护性。继承关系中的类形成层次结构,基类定义共性,派生类则根据需求添加特有功能。在继承时,需要注意成员函数的隐藏、作用域以及默认成员函数(的处理。此外,继承不支持友元关系的继承,静态成员在整个继承体系中是唯一的。虽然多继承和菱形继承可以提供复杂的设计,但它们可能导致二义性、数据冗余和性能问题,因此在实际编程中应谨慎使用。
17 1
【C++笔记】从零开始认识继承
|
19天前
|
C++
C++从入门到精通:2.1.2函数和类——深入学习面向对象的编程基础
C++从入门到精通:2.1.2函数和类——深入学习面向对象的编程基础
|
25天前
|
C++
面向对象的C++题目以及解法2
面向对象的C++题目以及解法2
31 1
|
25天前
|
C++
面向对象的C++题目以及解法
面向对象的C++题目以及解法
19 0
|
27天前
|
编译器 C语言 C++
【C++成长记】C++入门 | 类和对象(上) |面向过程和面向对象初步认识、类的引入、类的定义、类的访问限定符及封装
【C++成长记】C++入门 | 类和对象(上) |面向过程和面向对象初步认识、类的引入、类的定义、类的访问限定符及封装
|
1月前
|
存储 编译器 程序员
【C++】类和对象①(什么是面向对象 | 类的定义 | 类的访问限定符及封装 | 类的作用域和实例化 | 类对象的存储方式 | this指针)
【C++】类和对象①(什么是面向对象 | 类的定义 | 类的访问限定符及封装 | 类的作用域和实例化 | 类对象的存储方式 | this指针)