C++——类和对象(初始化列表和运算符重载与静态与友元)

文章目录

初始化列表

初始化列表(initializer list)在C++中是一种用来初始化类、结构体、数组等数据结构的语法结构。它可以在定义变量时直接初始化,也可以在构造函数中初始化成员变量。

语法结构

cpp 复制代码
ConstructorName(Type1 arg1, Type2 arg2, ...) : member1(arg1), member2(arg2), ... {
    // constructor body
}

这里是初始化列表的组成部分

ConstructorName:构造函数的名称,与类名相同。

参数列表:构造函数可以接受的参数列表,这些参数用来初始化类的成员变量。

冒号(:):初始化列表以冒号开头。

成员初始化:每个成员初始化的语法是 memberName(arg),其中 memberName 是类的成员变量名,arg 是用来初始化该成员变量的值。

构造函数体:可选的构造函数体,用来执行更多的初始化操作或者其他逻辑。

定义和使用

在变量定义时的初始化:

cpp 复制代码
int arr[3] = {1, 2, 3}; // 初始化数组
std::vector<int> vec = {4, 5, 6}; 
// 初始化向量在构造函数中的初始化:
class Example {
public:
    int x;
    double y;

    // 构造函数使用初始化列表
    Example(int a, double b) : x(a), y(b) {
        // 构造函数体
    }
};

在上面的例子中,Example类的构造函数使用初始化列表来初始化成员变量x和y。

案例

初始化列表初始化成员变量:

cpp 复制代码
class Point {
private:
    int x, y;

public:
    // 使用初始化列表初始化成员变量
    Point(int x_val, int y_val) : x(x_val), y(y_val) {
        // 可选的构造函数体
    }
};

初始化列表初始化常量成员:

cpp 复制代码
class Circle {
private:
    const double pi;
    double radius;

public:
    // 初始化常量成员 pi
    Circle(double r) : pi(3.14159), radius(r) {
        // 构造函数体
    }
};

注意事项

初始化顺序:初始化列表中的成员变量初始化顺序与它们在类中声明的顺序一致,而不是它们在初始化列表中出现的顺序。

初始化常量成员:常量成员变量(如上例中的pi)只能通过初始化列表来初始化,不能在构造函数体内赋值。

成员变量类型:初始化列表只能用于初始化非静态成员变量,静态成员变量不能在构造函数初始化列表中初始化。

没有默认值:如果类的某个成员变量没有在初始化列表中初始化,它将使用默认的构造函数进行初始化(如果有的话)。

效率:初始化列表的使用可以避免不必要的默认构造和拷贝操作,因此在效率上可能比在构造函数体内进行初始化更好。

运算符重载

加法运算符重载

语法结构

加法运算符 + 的重载语法如下:

cpp 复制代码
ReturnType operator+(const ClassName& obj) {
    // 加法运算的实现
    // 可以访问对象的成员变量和方法
    return ReturnType(result);
}

其中:

  • ReturnType:是加法运算的结果类型,可以是对象、引用或者基本数据类型。
  • operator+:是加法运算符的函数名,用于重载加法运算符。
  • ClassName:是定义加法运算符的类名。
  • const ClassName& obj:是右操作数,即用于与当前对象进行加法运算的对象。

函数体内部实现了加法的逻辑,并返回加法结果。

示例

下面是一个使用加法运算符重载的示例:

cpp 复制代码
#include <iostream>

class Complex {
private:
    double real;
    double imag;

public:
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}

    // 重载加法运算符+
    Complex operator+(const Complex& other) {
        Complex temp;
        temp.real = real + other.real;
        temp.imag = imag + other.imag;
        return temp;
    }

    void display() {
        std::cout << real << " + " << imag << "i" << std::endl;
    }
};

int main() {
    Complex c1(2.3, 4.5);
    Complex c2(3.4, 5.6);
    Complex c3;

    c3 = c1 + c2; // 使用重载的+运算符

    std::cout << "Sum of ";
    c1.display();
    std::cout << "and ";
    c2.display();
    std::cout << "is ";
    c3.display();

    return 0;
}

在这个例子中,Complex类表示复数,重载了加法运算符 +,使得可以对两个 Complex 对象进行相加操作。在 main() 函数中,创建了两个复数对象 c1 和 c2,然后通过 c1 + c2 进行加法运算,并将结果赋给 c3。

注意事项

参数类型:通常情况下,加法运算符重载的参数应当是同类的对象或者可以转换为同类对象的类型。

返回类型:加法运算符重载函数通常返回一个新的对象,代表加法的结果。

修改对象:一般情况下,不应该在加法运算符重载中修改参数对象本身的状态,而是应当返回一个新的对象表示结果。

友元函数:如果需要在加法运算符重载函数中访问私有成员变量,可以将其声明为友元函数。

连续加法:重载的加法运算符也支持链式调用,例如 c1 + c2 + c3。

保持语义一致性:加法运算符重载的结果应当符合加法的数学意义,并且不应引入歧义或者不合理的行为。

减法运算符重载

当涉及到在 C++ 中重载减法运算符 - 时,它与重载加法运算符类似,允许用户定义对自定义类型的减法操作。下面是减法运算符重载的语法结构、示例和一些注意事项:

语法结构

减法运算符 - 的重载语法如下:

cpp 复制代码
ReturnType operator-(const ClassName& obj) {
    // 减法运算的实现
    // 可以访问对象的成员变量和方法
    return ReturnType(result);
}

其中:

  • ReturnType:是减法运算的结果类型,可以是对象、引用或者基本数据类型。
  • operator-:是减法运算符的函数名,用于重载减法运算符。
  • ClassName:是定义减法运算符的类名。
  • const ClassName& obj:是右操作数,即用于与当前对象进行减法运算的对象。

函数体内部实现了减法的逻辑,并返回减法结果。

示例

下面是一个使用减法运算符重载的示例,假设我们仍然使用复数 Complex 类:

cpp 复制代码
#include <iostream>

class Complex {
private:
    double real;
    double imag;

public:
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}

    // 重载减法运算符-
    Complex operator-(const Complex& other) {
        Complex temp;
        temp.real = real - other.real;
        temp.imag = imag - other.imag;
        return temp;
    }

    void display() {
        std::cout << real << " + " << imag << "i" << std::endl;
    }
};

int main() {
    Complex c1(5.7, 8.4);
    Complex c2(3.2, 1.6);
    Complex c3;

    c3 = c1 - c2; // 使用重载的-运算符

    std::cout << "Difference of ";
    c1.display();
    std::cout << "and ";
    c2.display();
    std::cout << "is ";
    c3.display();

    return 0;
}

在这个示例中,Complex 类再次表示复数,重载了减法运算符 -,允许对两个 Complex 对象进行减法操作。在 main() 函数中,创建了两个复数对象 c1 和 c2,然后通过 c1 - c2 进行减法运算,并将结果赋给 c3。

注意事项

参数类型和返回类型:与加法运算符类似,减法运算符重载函数的参数应当是同类的对象或者可以转换为同类对象的类型,返回类型通常是表示减法结果的新对象。

对象状态:遵循良好的实践,不要在减法运算符重载中修改参数对象本身的状态,而是应当返回一个新的对象表示结果。

友元函数:如有需要,可以将减法运算符重载函数声明为友元函数以访问私有成员变量。

连续减法:重载的减法运算符同样支持链式调用,例如 c1 - c2 - c3。

语义一致性:确保减法运算符重载的行为符合减法的数学定义,避免引入歧义或者不合理的操作。

等于运算符重载

当你在 C++ 中重载等于运算符 ==,你可以自定义两个对象相等的条件。这种重载允许你用更符合你自定义类型特性的方式来比较对象的相等性。

等于运算符 == 的重载语法

等于运算符的重载语法如下:

cpp 复制代码
bool operator==(const ClassName& obj) const {
    // 判断当前对象和参数对象是否相等的逻辑
    // 返回 true 表示相等,返回 false 表示不相等
}

其中:

  • bool:等于运算符需要返回一个布尔值,表示对象是否相等。
  • operator==:是等于运算符的函数名,用于重载等于运算符。
  • ClassName:是定义等于运算符的类名。
  • const ClassName& obj:是右操作数,即用于与当前对象进行比较的对象。

const 关键字和成员函数后面的 const 保证了该成员函数不会修改对象的成员变量。

示例

假设我们继续使用复数 Complex 类来示范等于运算符的重载:

cpp 复制代码
#include <iostream>

class Complex {
private:
    double real;
    double imag;

public:
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}

    // 重载等于运算符==
    bool operator==(const Complex& other) const {
        return (real == other.real) && (imag == other.imag);
    }

    void display() const {
        std::cout << real << " + " << imag << "i" << std::endl;
    }
};

int main() {
    Complex c1(2.0, 3.0);
    Complex c2(2.0, 3.0);
    Complex c3(4.0, 1.5);

    if (c1 == c2) {
        std::cout << "c1 and c2 are equal." << std::endl;
    } else {
        std::cout << "c1 and c2 are not equal." << std::endl;
    }

    if (c1 == c3) {
        std::cout << "c1 and c3 are equal." << std::endl;
    } else {
        std::cout << "c1 and c3 are not equal." << std::endl;
    }

    return 0;
}

在这个示例中,Complex 类重载了等于运算符 ==,以便比较两个复数对象的实部和虚部是否相等。在 main() 函数中,创建了三个复数对象 c1、c2 和 c3,并使用 c1 == c2 和 c1 == c3 来演示等于运算符的使用。

注意事项

语义一致性:确保你的等于运算符重载与你的类型的语义一致。比如,在复杂类型中,可能需要比较多个成员变量而不仅仅是一个。

友元函数:如有必要,等于运算符重载可以声明为友元函数,以便访问私有成员变量。

对称性:等于运算符应当是对称的,即 a == b 应当和 b == a 具有相同的效果。

大于运算符重载

大于运算符 > 的重载语法

大于运算符的重载语法与等于运算符类似,它允许您自定义两个对象之间的大小比较条件。

cpp 复制代码
bool operator>(const ClassName& obj) const {
    // 判断当前对象是否大于参数对象的逻辑
    // 返回 true 表示当前对象大于参数对象,返回 false 表示不大于
}

其中:

  • bool:大于运算符需要返回一个布尔值,表示当前对象是否大于参数对象。
  • operator>:是大于运算符的函数名,用于重载大于运算符。
  • ClassName:是定义大于运算符的类名。
  • const ClassName& obj:是右操作数,即用于与当前对象进行比较的对象。

const 关键字和成员函数后面的 const 保证了该成员函数不会修改对象的成员变量。

示例

继续使用之前的 Complex 类来演示大于运算符的重载:

cpp 复制代码
#include <iostream>

class Complex {
private:
    double real;
    double imag;

public:
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}

    // 重载大于运算符>
    bool operator>(const Complex& other) const {
        // 比较复数的模(模长)
        double this_modulus = real * real + imag * imag;
        double other_modulus = other.real * other.real + other.imag * other.imag;
        return this_modulus > other_modulus;
    }

    void display() const {
        std::cout << real << " + " << imag << "i" << std::endl;
    }
};

int main() {
    Complex c1(2.0, 3.0);
    Complex c2(4.0, 1.5);

    if (c1 > c2) {
        std::cout << "c1 is greater than c2." << std::endl;
    } else {
        std::cout << "c1 is not greater than c2." << std::endl;
    }

    return 0;
}

在这个示例中,Complex 类重载了大于运算符 >,以便比较两个复数对象的模(即模长)是否满足大于关系。在 main() 函数中,创建了两个复数对象 c1 和 c2,并使用 c1 > c2 来演示大于运算符的使用。

注意事项

语义一致性:确保您的大于运算符重载与您的类型的语义一致。比如,在复杂类型中,可能需要比较多个成员变量而不仅仅是一个。

友元函数:如有必要,大于运算符重载可以声明为友元函数,以便访问私有成员变量。

对称性:大于运算符应当是对称的,即 a > b 应当和 b < a 具有相同的效果。

++运算符重载

当我们讨论 "++" 运算符在 C++ 中的重载时,通常会涉及到两种形式:前置加加运算符(++operand)和后置加加运算符(operand++)。下详细解释这两种运算符的重载语法结构、定义、案例和注意事项。

前置加加运算符 (++operand)

语法结构和定义

前置加加运算符 ++ 的重载形式如下:

cpp 复制代码
ReturnType operator++() {
    // 增加操作数的值
    // 返回增加后的操作数
}

其中:

  • ReturnType:是返回值的类型,通常是引用类型,以便支持连续操作。
  • operator++:是前置加加运算符的函数名,用于重载前置加加运算符。
  • ():代表这是一个函数调用。

操作数被隐式地作为当前对象(this)传递给运算符函数。

示例

以下是一个示例,演示了如何重载前置加加运算符:

cpp 复制代码
#include <iostream>

class Integer {
private:
    int value;

public:
    Integer(int v = 0) : value(v) {}

    // 重载前置加加运算符++
    Integer& operator++() {
        ++value;
        return *this;
    }

    void display() const {
        std::cout << "Value: " << value << std::endl;
    }
};

int main() {
    Integer num(5);

    ++num;  // 使用前置加加运算符
    num.display();  // 输出 Value: 6

    return 0;
}

在这个示例中,Integer 类重载了前置加加运算符 ++,使得可以直接对 Integer 类型的对象进行自增操作。

注意事项(前置加加运算符)

返回类型:通常应该返回引用类型 ReturnType&,以支持链式调用和修改对象本身。

副作用:前置加加运算符会直接修改对象的值,应该确保操作的一致性和正确性。

成员函数声明:通常应将前置加加运算符声明为类的成员函数,以便访问对象的私有成员变量。

后置加加运算符 (operand++)

语法结构和定义

后置加加运算符 ++ 的重载形式如下:

cpp 复制代码
ReturnType operator++(int) {
    // 创建一个副本以便于后续返回
    // 增加操作数的值
    // 返回之前的副本
}

其中:

  • ReturnType:是返回值的类型,通常是原始类型(非引用)。
  • operator++:是后置加加运算符的函数名,用于重载后置加加运算符。
  • (int):这是一个额外的参数,但通常不使用该参数。
示例

以下是一个示例,演示了如何重载后置加加运算符:

cpp 复制代码
#include <iostream>

class Integer {
private:
    int value;

public:
    Integer(int v = 0) : value(v) {}

    // 重载后置加加运算符++
    Integer operator++(int) {
        Integer temp(value);  // 创建一个副本

        ++value;  // 增加操作数的值

        return temp;  // 返回之前的副本
    }

    void display() const {
        std::cout << "Value: " << value << std::endl;
    }
};

int main() {
    Integer num(5);

    Integer old_num = num++;  // 使用后置加加运算符
    num.display();  // 输出 Value: 6
    old_num.display();  // 输出 Value: 5

    return 0;
}

在这个示例中,Integer 类重载了后置加加运算符 ++,返回原始值的副本,并在返回之前增加了对象的值。

注意事项(后置加加运算符)

返回类型 :通常应该返回对象的原始类型,而不是引用,以避免悬挂引用问题。
参数 :后置加加运算符通常需要一个额外的 int 参数(可以不使用),用于区分前置和后置形式。
效率:后置加加运算符需要创建一个对象的副本,因此可能比前置加加运算符稍慢。

<<运算符重载

重载左移运算符 << 在 C++ 中常用于自定义类的输出格式,使得类对象可以像内置类型一样直接输出到标准输出流(如 std::cout)。下面详细解释如何在 C++ 中重载左移运算符 <<。

语法结构和定义

左移运算符 << 的重载形式如下:

cpp 复制代码
ReturnType operator<<(std::ostream& os, const YourClass& obj) {
    // 输出 obj 到 ostream os 中
    return os;  // 返回 ostream 对象
}

其中:

  • ReturnType:通常是 std::ostream&,因为左移运算符重载的目的是为了支持链式输出。
  • operator<<:是左移运算符的函数名,用于重载左移运算符。
  • std::ostream& os:是输出流对象的引用,如 std::cout。
  • const YourClass& obj:是要输出的类对象的引用,通常是常量引用以避免不必要的复制。

示例

以下是一个示例,演示了如何重载左移运算符 <<:

cpp 复制代码
#include <iostream>

class Point {
private:
    int x;
    int y;

public:
    Point(int x = 0, int y = 0) : x(x), y(y) {}

    // 重载左移运算符<<
    friend std::ostream& operator<<(std::ostream& os, const Point& obj) {
        os << "Point(" << obj.x << ", " << obj.y << ")";
        return os;
    }
};

int main() {
    Point p1(3, 4);
    Point p2(-1, 8);

    std::cout << "Point p1: " << p1 << std::endl;
    std::cout << "Point p2: " << p2 << std::endl;

    return 0;
}

在这个示例中,Point 类重载了左移运算符 <<,使得可以直接输出 Point 对象到标准输出流 std::cout 中。

注意事项

友元函数:通常将左移运算符重载函数声明为类的友元函数,以便访问类的私有成员变量。

返回类型:应该返回 std::ostream& 类型,以支持链式输出。

引用参数:对象参数应该是常量引用 const YourClass&,避免不必要的复制。

静态变量和函数

在C++中,静态变量(静态成员变量)和静态函数(静态成员函数)属于类的静态成员,它们不依赖于类的实例而存在,而是与类本身关联。以下是它们的语法结构、定义、案例和注意事项:

静态变量(静态成员变量)

语法结构和定义:

静态成员变量由关键字 static 声明,通常在类的声明中定义,但在类的定义外初始化。

定义格式为:static type variable_name;

cpp 复制代码
class MyClass {
public:
    static int count;
    // other members...
};

// 类的定义外部初始化
int MyClass::count = 0;

案例:

cpp 复制代码
#include <iostream>

class MyClass {
public:
    static int count;

    MyClass() {
        count++;
    }
};

int MyClass::count = 0;

int main() {
    MyClass obj1;
    MyClass obj2;
    MyClass obj3;

    std::cout << "Number of objects created: " << MyClass::count << std::endl;

    return 0;
}

输出:

Number of objects created: 3

注意事项:

  • 静态变量被所有类对象共享,它们在程序的生命周期内只有一份实例。

  • 静态成员变量必须在类的定义外部进行初始化。

  • 可以通过类名和作用域解析运算符 :: 来访问静态成员变量,如 MyClass::count。

静态函数(静态成员函数)

语法结构和定义:

静态成员函数也由关键字 static 声明。

静态函数可以直接访问类的静态成员变量和其他静态成员函数,但不能直接访问非静态成员变量和非静态成员函数。

定义格式为:static return_type function_name(parameters);
cpp 复制代码
class MyClass {
public:
    static void staticFunction() {
        std::cout << "This is a static function." << std::endl;
    }
};

案例:

cpp 复制代码
#include <iostream>

class MyClass {
public:
    static void staticFunction() {
        std::cout << "This is a static function." << std::endl;
    }

    void normalFunction() {
        std::cout << "This is a normal function." << std::endl;
    }
};

int main() {
    MyClass::staticFunction();

    MyClass obj;
    obj.normalFunction();

    // Accessing static function through object (not recommended):
    obj.staticFunction(); // valid but not recommended

    return 0;
}

输出

This is a static function.

This is a normal function.

This is a static function.

注意事项:

  • 静态函数不能直接访问非静态成员变量或非静态成员函数,因为它们没有隐含的 this 指针。

  • 可以通过类名直接调用静态函数,也可以通过对象调用静态函数,但不推荐后者,因为静态函数不依赖于具体对象。

友元

友元(friend)在C++中是一种机制,允许某些函数或类访问另一个类的私有成员。友元可以是一个函数、一个类、或者整个类中的所有函数。这种机制打破了C++中的封装性,因此需要谨慎使用。

友元函数

语法结构和定义:

友元函数声明在目标类的内部,但定义在类的外部。

友元函数的声明以关键字 friend 开始,后面跟随函数原型。
cpp 复制代码
class MyClass {
private:
    int privateData;

public:
    MyClass() : privateData(0) {}

    friend void friendFunction(MyClass &obj);
};

void friendFunction(MyClass &obj) {
    // 友元函数可以访问目标类的私有成员
    obj.privateData = 100;
}

案例:

cpp 复制代码
#include <iostream>

class MyClass {
private:
    int privateData;

public:
    MyClass() : privateData(0) {}

    friend void friendFunction(MyClass &obj);

    void displayPrivateData() {
        std::cout << "Private data: " << privateData << std::endl;
    }
};

void friendFunction(MyClass &obj) {
    obj.privateData = 100; // 可以访问私有成员 privateData
}

int main() {
    MyClass obj;
    friendFunction(obj); // 调用友元函数

    obj.displayPrivateData(); // 输出 Private data: 100

    return 0;
}

输出

Private data: 100

注意事项:

  • 友元函数并不是目标类的成员函数,它没有 this 指针,因此不能直接访问类的成员。
  • 友元函数在访问目标类的私有成员时不受访问权限限制。
  • 友元函数的声明需要在目标类中进行,但定义可以在类外部进行。
    友元类

除了友元函数,C++还支持友元类,即一个类可以将另一个类声明为自己的友元,这样被声明的友元类就可以访问声明它为友元的类的所有成员,包括私有成员。

友元类

声明

cpp 复制代码
class FriendClass {
public:
    void accessMyClassPrivateData(MyClass &obj) {
        obj.privateData = 200; // 可以访问目标类的私有成员
    }
};

class MyClass {
private:
    int privateData;

    friend class FriendClass; // 声明FriendClass为友元类

public:
    MyClass() : privateData(0) {}

    void displayPrivateData() {
        std::cout << "Private data: " << privateData << std::endl;
    }
};

案例:

cpp 复制代码
#include <iostream>

class FriendClass {
public:
    void accessMyClassPrivateData(MyClass &obj) {
        obj.privateData = 200; // 可以访问目标类的私有成员
    }
};

class MyClass {
private:
    int privateData;

    friend class FriendClass; // 声明FriendClass为友元类

public:
    MyClass() : privateData(0) {}

    void displayPrivateData() {
        std::cout << "Private data: " << privateData << std::endl;
    }
};

int main() {
    MyClass obj;
    FriendClass friendObj;

    friendObj.accessMyClassPrivateData(obj); // 通过友元类访问私有成员

    obj.displayPrivateData(); // 输出 Private data: 200

    return 0;
}

输出

 Private data: 200

注意事项:

  • 友元类的声明会影响整个类的封装性,因此应慎重考虑使用。
  • 友元关系是单向的,声明了 A 类为 B 类的友元并不意味着 B 类也是 A 类的友元。
  • 友元关系不能被继承,子类不能访问父类的友元。
相关推荐
Ajiang28247353045 分钟前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
幽兰的天空10 分钟前
Python 中的模式匹配:深入了解 match 语句
开发语言·python
Theodore_10223 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
‘’林花谢了春红‘’5 小时前
C++ list (链表)容器
c++·链表·list
----云烟----5 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024065 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
开心工作室_kaic6 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it6 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
武子康6 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
转世成为计算机大神6 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式