目录
函数重载
函数重载允许我们在同一作用域内定义多个同名函数,只要它们的参数列表不同即可。编译器会根据调用时提供的参数类型和数量来决定调用哪个版本的函数。
参数类型不同:
#include <iostream>
#include <string>
// 打印整数的函数
void print(int value) {
std::cout << "整数: " << value << std::endl;
}
// 打印浮点数的函数
void print(double value) {
std::cout << "浮点数: " << value << std::endl;
}
// 打印字符串的函数
void print(const std::string& value) {
std::cout << "字符串: " << value << std::endl;
}
int main() {
print(42); // 调用print(int)
print(3.14159); // 调用print(double)
print("Hello"); // 调用print(const std::string&)
return 0;
}
参数数量不同:
void fun()
{
cout << "fun()" << endl;
}
void fun(int a)
{
cout << "fun(int a)" << endl;
}
函数重载的注意事项
仅返回类型不同不构成重载
参数类型或数量必须不同
重载解析发生在编译时
注意避免重载导致的二义性
缺省参数
缺省参数允许函数在定义时为参数指定默认值。如果调用函数时没有提供该参数的值,就会使用默认值。
缺省参数分为全缺省和半缺省。
缺省参数的规则
缺省参数必须从右向左连续设置
通常在函数声明中指定缺省参数(而非定义)
每个缺省参数只能指定一次(通常在头文件中)
缺省值可以是全局变量、常量或函数调用
引用
引用的基本概念
引用本质上是一个变量的别名,它为已存在的变量提供了另一个名称。在C++中,我们使用&
符号来声明引用:
int original = 42;
int& ref = original; // ref是original的引用
引用与指针的区别
虽然引用和指针在某些方面相似,但它们有几个关键区别:
初始化要求:引用必须在声明时初始化,而指针可以不初始化(尽管不推荐)。
可修改性:引用一旦初始化就不能改变指向,指针可以。
空值:引用不能为NULL或nullptr,指针可以。
语法:引用使用起来像普通变量,指针需要解引用操作。
int x = 10;
int y = 20;
// 指针
int* p = &x;
p = &y; // 可以改变指向
// 引用
int& r = x;
// r = y; // 这不会改变r的引用,而是把y的值赋给x
引用的主要用途
1. 函数参数传递(按引用传递)
引用最常见的用途是在函数参数传递中,允许函数修改调用者的变量:
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 5, y = 10;
swap(x, y); // x和y的值被交换
}
2. 避免对象拷贝
对于大型对象,按值传递会导致昂贵的拷贝操作。使用引用可以避免这种开销:
void processLargeObject(const LargeObject& obj) {
// 不会拷贝LargeObject,同时保证obj不会被修改
}
3. 函数返回值
函数可以返回引用,但必须确保返回的引用指向的对象在函数返回后仍然存在:
int& getElement(std::vector<int>& arr, size_t index) {
return arr[index];
}
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
getElement(numbers, 2) = 10; // 修改numbers[2]为10
}
常量引用
常量引用(const T&
)允许我们传递参数而不允许修改它们,同时避免拷贝:
void printString(const std::string& str) {
std::cout << str;
// str不能被修改
}
引用使用的注意事项
不要返回局部变量的引用:局部变量在函数结束后会被销毁,返回它们的引用会导致未定义行为。
引用不是对象:不能创建引用的数组、指向引用的指针或引用的引用(除非使用类型别名)。
接口设计:当函数需要修改参数时使用非常量引用,否则使用常量引用。
类
1.1 什么是类?
类(Class)是C++中实现面向对象编程的基本单元,它是用户自定义的数据类型,包含数据成员和成员函数:
class Rectangle {
private:
double width; // 数据成员
double height;
public:
// 成员函数
void setDimensions(double w, double h) {
width = w;
height = h;
}
double area() const {
return width * height;
}
};
1.2 类与结构体的区别
在C++中,class
和struct
非常相似,主要区别在于默认访问权限:
class
:默认成员是privatestruct
:默认成员是public
class MyClass { // 默认private
int x; // private
};
struct MyStruct { // 默认public
int x; // public
};
1.3 访问控制
C++提供三种访问修饰符:
public:在任何地方都可以访问
private:只能在类内部访问
protected:类内部和派生类中可以访问
class AccessExample {
public:
int publicVar; // 任何代码可访问
private:
int privateVar; // 仅类成员函数可访问
protected:
int protectedVar; // 类成员和派生类可访问
};
类的静态成员
静态成员属于类本身而非类的实例:
class Counter {
private:
static int count; // 静态数据成员声明
public:
Counter() { ++count; }
~Counter() { --count; }
static int getCount() { return count; } // 静态成员函数
};
int Counter::count = 0; // 静态数据成员定义
this指针
1、this指针在栈区,成员函数有一个隐式形参 类名*const this 只能在成员函数内部使用。
2、this 指针本质上是 “ 成员函数 ” 的形参 ,当对象调用成员函数时,将对象地址作为实参传递给
this 形参。所以 对象中不存储this指针 。
3、 this 指针是 “ 成员函数 ” 第一个隐含的指针形参,一般情况由编译器通过 ecx 寄存器自动传
递,不需要用户传递。
编译器编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类类型的指针,叫做this
指针。比如Date类的Init的真实原型为, void Init(Date* const this, int year, int month, int day)
• 类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值, this-
>_year = year;
• C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针。
#include<iostream>
using namespace std;
class Date
{
public:
// void Init(Date* const this, int year, int month, int day)
void Init(int year, int month, int day)
{
// 编译报错:error C2106: “=”: 左操作数必须为左值
// this = nullptr;
// this->_year = year;
_year = year;
this->_month = month;
this->_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
// 这里只是声明,没有开空间
int _year;
int _month;
int _day;
};
int main()
{
// Date类实例化出对象d1和d2
Date d1;
Date d2;
// d1.Init(&d1, 2024, 3, 31);
d1.Init(2024, 3, 31);
d1.Print();
d2.Init(2024, 7, 5);
d2.Print();
return 0;
}
成员函数
C++类有6个特殊成员函数,编译器会在需要时自动生成:
默认构造函数
析构函数
拷贝构造函数
拷贝赋值运算符
移动构造函数(C++11)
移动赋值运算符(C++11)
构造函数
构造函数(一般写成全缺省的)是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。
构造函数的主要任务并不是创建对象,而是初始化对象。
特征如下:
- 函数名与类名相同
- 无返回值
- 对象实例化时编译器自动调用对应的构造函数
- 构造函数可以重载
class Date
{
public:
Date(int year = 1, int month = 1, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1();
Date d2(2025, 3, 19);
Date d3(2024, 3);
return 0;
}
默认生成构造函数:对内置类型成员不做初始化(因此我们要自己写构造函数)。自定义类型成员(class和struct)可以自动调用默认构造函数(它自己的构造函数)。
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意,无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
析构函数
析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,比如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++ 规定对象在销毁时会自动调用析构函数,完成对象中的资源的清理释放工作。
析构函数的特点:
- 析构函数名是在类名前加上字符~
- 无参数无返回值(也不需要加void)
- 一个类只能有一个析构函数。若未显示定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,系统会自动调用析构函数
- 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型不做处理,自定义类型成员会调用它的析构函数
- 要注意的是,我们显示写析构函数,对于自定义类型成员也会调用它的析构函数,也就是说自定义类型成员无论什么情况都会自动调用析构函数。
- 一个局部域的多个对象,C++规定后定义的先析构。
运算符重载
运算符重载是具有特殊名字的函数,他的名字是有operator和后面要定义的运算符共同构成。和其他函数一样,它也具有其返回类型和参数列表以及函数体。
重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。
如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少一个。
运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致。
//判断两个日期是否相等
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
private:
int _year;
int _month;
int _day;
};