Logo

郎哥编程

C++操作符

2026-11-04 4

C++操作符(Operator)是用于执行程序代码运算的符号,其中算术操作符、关系操作符、逻辑操作符、赋值操作符、位操作符与C语言完全相同,这部分内容不再讲述,重点讲述C++操作符特有的操作。

1.流操作符

1.1.基本定义和用法

在 C++ 的输入输出体系中,最常用的两个流操作符是流插入运算符 “<<” 和流提取运算符 “>>” 。“<<” 负责将数据从变量传输到输出流中,如标准输出流cout,它通常对应着显示器,也可以是文件流,用于将数据写入文件。而 “>>” 则从输入流中获取数据并传输给变量,常如标准输入流cin,主要用于接收来自键盘的输入数据,当然也能从文件流中读取数据。简单来说,“<<” 用于输出数据,让程序的内容得以呈现;“>>” 用于输入数据,使程序能够获取外部的信息 。这两个流操作符是 C++ 进行数据输入输出的基础工具,在程序中频繁使用。

下面通过一段简单的代码示例,来看看流操作符的用法。假设我们要从键盘读取一个人的年龄和工资信息,并将其输出显示 :​

#include <iostream>​
using namespace std;​
​
int main() {​
    int age;​
    double salary;​
​
    // 输入数据​
    cout << "请输入你的年龄和工资:";​
    cin >> age >> salary;​
​
    // 输出数据​
    cout << "你的年龄是:" << age << endl;​
    cout << "你的工资是:" << salary << endl;​
​
    return 0;​
}​
​

​在上述代码中,首先使用cout和 “<<” 输出提示信息,引导用户输入数据。然后通过cin和 “>>” 从键盘读取用户输入的年龄和工资数据,并分别存储到age和salary变量中。最后,使用cout和 “<<” 将读取到的数据输出显示。

1.2.流操作符的重载机制

内置类型支持​

C++已经为我们处理好了基本类型的流操作符重载。对于像int、double、char等基本数据类型,我们可以直接使用 “<<” 和 “>>” 进行输入输出操作,无需手动重载。例如,当我们使用cout << 10;时,背后调用的就是标准库中为int类型重载的 “<<” 操作符函数 。这些典型的函数原型如下:​

​ostream& operator<<(ostream& os, int value);​
istream& operator>>(istream& is, int& value);​

对于double类型,同样有对应的重载函数:​

ostream& operator<<(ostream& os, double value);​
istream& operator>>(istream& is, double& value);​

以此类推,对于其他基本类型,也都有类似的重载函数。

自定义类型重载步骤​

当涉及到自定义类型时,就需要手动重载流操作符,下面以一个简单的日期类Date为例,详细讲解自定义类型重载流操作符的步骤。​

(1)自定义类型

class Date {​
private:​
    int year;​
    int month;​
    int day;​
public:​
    // 构造函数​
    Date(int y, int m, int d) : year(y), month(m), day(d) {}​
    // 友元声明​
    friend ostream& operator<<(ostream& os, const Date& d);​
    friend istream& operator>>(istream& is, Date& d);​
};​
​

在Date类中,声明了两个友元函数,分别用于重载输出流操作符 “<<” 和输入流操作符 “>>” 。使用友元函数,是因为这样可以让函数访问类的私有成员,方便在重载函数中对日期的年、月、日进行操作。如果不使用友元函数,由于操作符重载函数的第一个参数不是类对象本身(this指针所指向的对象),无法直接访问类的私有成员,就需要通过类的公有接口来访问,这会增加代码的复杂性和冗余度。​

1.3.流操作符重载

ostream& operator<<(ostream& os, const Date& d) {​
    os << d.year << "-" << d.month << "-" << d.day;​
    return os;​
}​

​在上面的函数中,将日期对象d的年、月、日按照指定的格式输出到输出流os中,然后返回输出流对象os,这样做是为了支持链式调用,例如如cout << date1 << " " << date2;,如果不返回流对象的引用,就无法实现这种连续的输出操作 。​

实现输入流操作符 “>>” 的重载,同时加入一些简单的输入校验:​

​

istream& operator>>(istream& is, Date& d) {​
    char dash1, dash2;​
    is >> d.year >> dash1 >> d.month >> dash2 >> d.day;​
    if (dash1 != '-' || dash2 != '-' || d.month < 1 || d.month > 12) {​
        is.setstate(ios::failbit); // 标记错误状态​
    }​
    return is;​
}​

在上面的函数中,首先从输入流is中读取年、月、日的数据,同时读取分隔符 “-” 。然后进行输入校验,如果分隔符不是 “-”,或者月份不在 1 到 12 的范围内,就将输入流的状态设置为错误状态,这样后续的输入操作可以根据流的状态来判断是否输入正确 。最后返回输入流对象is,同样是为了支持链式调用,例如cin >> date1 >> date2;。

2.成员访问操作符

在C++中,成员访问操作符主要用于类和结构体的成员访问,主要有两个成员访问操作符:点操作符(.)和箭头操作符(->)。

2.1.点操作符

点操作符在 C++ 中用于访问对象的成员,无论是成员变量还是成员函数。假设我们正在开发一个简单的图形绘制库,定义一个Circle类来表示圆:​

​

class Circle {​
public:​
    double radius;​
    double x, y; // 圆心坐标​
    double area() {​
        return 3.14159 * radius * radius;​
    }​
};​

​当我们创建一个Circle对象时,就可以使用点操作符来访问它的成员:​

​

Circle myCircle;​
myCircle.radius = 5.0;​
myCircle.x = 10.0;​
myCircle.y = 10.0;​
double circleArea = myCircle.area();​

​在上述代码中,myCircle.radius和myCircle.x、myCircle.y是通过点操作符访问对象的成员变量,而myCircle.area()则是通过点操作符调用对象的成员函数。

2.2.箭头操作符

箭头操作符(->)主要用于通过指针访问对象的成员,它的语法形式为指针变量->成员名。假设我们正在编写一个简单的数据库连接类DatabaseConnection :​

​class DatabaseConnection {​
public:​
    std::string host;​
    int port;​
    void connect() {​
        std::cout << "连接到数据库:" << host << ":" << port << std::endl;​
    }​
};​

​当使用指针来操作这个类时:​

​

DatabaseConnection* dbConn = new DatabaseConnection;​
dbConn->host = "localhost";​
dbConn->port = 3306;​
dbConn->connect();​
delete dbConn;​

这里的dbConn是一个指向DatabaseConnection对象的指针,通过dbConn->host、dbConn->port来访问对象的成员变量,通过dbConn->connect()来调用对象的成员函数。与点操作符不同的是,箭头操作符的左边必须是一个指针,而点操作符的左边是一个具体的对象。 如果我们这样写DatabaseConnection dbConn;,那么访问成员就需要使用点操作符dbConn.host、dbConn.port和dbConn.connect() 。

2.3.两者的区别

(1)使用方式差异

点操作符和箭头操作符最明显的区别就在于它们的操作数类型不同。点操作符(.)用于访问对象的成员,其左边必须是一个对象,无论是类的实例对象还是结构体对象。例如:​​

class Book {​
public:​
    std::string title;​
    std::string author;​
    void displayInfo() {​
        std::cout << "书名: " << title << ", 作者: " << author << std::endl;​
    }​
};​
​
Book myBook;​
myBook.title = "C++ Primer";​
myBook.author = "Stanley Lippman";​
myBook.displayInfo(); ​

​在上面的例子中,myBook是Book类的一个对象,通过点操作符可以直接访问其成员变量title、author和成员函数displayInfo 。

箭头操作符(->)用于通过指针访问对象的成员,其左边必须是一个指针。例如:​

​

Book* pBook = new Book;​
pBook->title = "Effective C++";​
pBook->author = "Scott Meyers";​
pBook->displayInfo();​
delete pBook; ​

​

这里pBook是指向Book对象的指针,通过箭头操作符来访问指针所指向对象的成员。如果错误地使用点操作符,如pBook.title,编译器会报错,因为点操作符期望的是一个对象,而不是指针。同样,如果使用箭头操作符在对象上,如myBook->title,也会导致编译错误,因为箭头操作符要求左边是指针。

(2)内存和寻址差异

从内存和寻址来看,点操作符和箭头操作符的工作方式也有所不同。当使用点操作符访问对象成员时,编译器会直接在对象的内存区域中找到对应的成员。因为对象在内存中是一块连续的存储区域,成员变量和成员函数的地址在编译时就已经确定,通过对象的首地址加上成员的偏移量,就可以直接访问到成员。例如,对于前面定义的Book类对象myBook ,假设myBook的首地址为0x1000,title成员变量在对象中的偏移量为0(假设title是第一个成员变量),那么访问myBook.title时,编译器会直接从地址0x1000处开始读取title的数据。​

而箭头操作符的寻址过程则稍微复杂一些。当使用箭头操作符通过指针访问对象成员时,首先要解引用指针,得到指针所指向对象的内存地址,然后再按照与点操作符相同的方式,在该对象的内存区域中访问成员。例如对于指针pBook ,假设pBook的值为0x2000,这是它所指向的Book对象的地址。当执行pBook->title时,首先解引用pBook,得到对象的地址0x2000,然后从地址0x2000处开始,按照title成员变量在对象中的偏移量来访问title的数据。本质上,pBook->title等价于(*pBook).title ,先通过*pBook解引用指针得到对象,再使用点操作符访问对象的成员。

3.new和delete操作符

在 C++ 中,new和delete操作符用于在堆上分配和释放内存。new操作符用于在堆上分配内存,delete操作符用于释放由new分配的内存。当我们使用new分配的内存不再需要时,就应该使用delete来释放,以避免内存泄漏。

3.1.new操作符

new操作符用于在堆上分配内存。其基本语法为:

type* pointer = new  type;

这里type是要分配内存的数据类型,例如int、double、自定义的类等,pointer是一个指针,用于指向分配的内存地址。例如,当我们需要为一个int型变量分配内存时,可以这样写:

int* numPtr = new int;​

这行代码在堆上为一个int型变量分配了内存,并将该内存的地址赋给了numPtr指针。之后,可以通过*numPtr来访问和操作这块内存,例如给它赋值:​

​*numPtr = 10;​

如果要分配一个数组的内存,new的语法变为:

type* arrayPtr = new type[size];

其中size表示数组元素的个数。例如分配一个包含 5 个int型元素的数组内存:​

int* arrPtr = new int[5];​

通过arrPtr[i]可以访问和操作数组中的每个元素,例如:​​

arrPtr[2] = 20;​

3.2.delete操作符

delete操作符用于释放由new分配的内存。当我们使用new分配的内存不再需要时,就应该使用delete来释放,以避免内存泄漏。​

对于单个变量的内存释放,语法为:

delete pointer;

例如,释放前面分配的int型变量的内存:​

delete numPtr;​

而对于数组内存的释放,必须使用delete[],语法为:

delete[] arrayPtr; 

如果错误地使用delete来释放数组内存,会导致内存释放不完整,引发内存泄漏等问题。例如,释放前面分配的数组内存:​

delete[] arrPtr;​

3.3.应用案例

下面通过一个代码案例,来展示new和delete在实际编程中的使用:​

#include <iostream>​
​
int main() {​
    // 动态分配一个int型变量​
    int* num = new int;​
    *num = 100;​
    std::cout << "num的值为:" << *num << std::endl;​
​
    // 动态分配一个包含5个元素的int型数组​
    int* arr = new int[5];​
    for (int i = 0; i < 5; ++i) {​
        arr[i] = i * 2;​
    }​
    std::cout << "数组元素为:";​
    for (int i = 0; i < 5; ++i) {​
        std::cout << arr[i] << " ";​
    }​
    std::cout << std::endl;​
​
    // 释放内存​
    delete num;​
    delete[] arr;​
​
    return 0;​
}​

​

在这个案例中,首先使用new分配了一个int型变量和一个包含 5 个元素的int型数组的内存,然后对它们进行了赋值和操作,最后使用delete和delete[]分别释放了它们的内存。

评论区

请 登录 后发表评论
暂无评论