C++第4课:操作符
- C++
- 1天前
- 24热度
- 0评论
C++操作符(Operator)是用于执行程序代码运算的符号,其中算术操作符、关系操作符、逻辑操作符、赋值操作符、位操作符与C语言完全相同,这部分内容不再讲述,重点讲述C++操作符特有的操作。
流操作符
基本定义和用法
在 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和 “<<” 将读取到的数据输出显示。
流操作符的重载机制
内置类型支持
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指针所指向的对象),无法直接访问类的私有成员,就需要通过类的公有接口来访问,这会增加代码的复杂性和冗余度。
流操作符重载
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;。
成员访问操作符
在C++中,成员访问操作符主要用于类和结构体的成员访问,主要有两个成员访问操作符:点操作符(.)和箭头操作符(->)。
点操作符
点操作符在 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()则是通过点操作符调用对象的成员函数。
箭头操作符
箭头操作符(->)主要用于通过指针访问对象的成员,它的语法形式为指针变量->成员名。假设我们正在编写一个简单的数据库连接类
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() 。
两者的区别
(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解引用指针得到对象,再使用点操作符访问对象的成员。
new和delete操作符
在 C++ 中,new和delete操作符用于在堆上分配和释放内存。new操作符用于在堆上分配内存,delete操作符用于释放由new分配的内存。当我们使用new分配的内存不再需要时,就应该使用delete来释放,以避免内存泄漏。
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;
delete操作符
delete操作符用于释放由new分配的内存。当我们使用new分配的内存不再需要时,就应该使用delete来释放,以避免内存泄漏。
对于单个变量的内存释放,语法为:
delete pointer;
例如,释放前面分配的int型变量的内存:
delete numPtr;
而对于数组内存的释放,必须使用delete[],语法为:
delete[] arrayPtr;
如果错误地使用delete来释放数组内存,会导致内存释放不完整,引发内存泄漏等问题。例如,释放前面分配的数组内存:
delete[] arrPtr;
应用案例
下面通过一个代码案例,来展示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[]分别释放了它们的内存。