C++ 面向对象 - 默认值与常量成员
一、 默认值
在C++中,默认值(Default Value)是一个重要的特性,它可以应用于多个场景,包括函数参数、类成员变量、模板参数、枚举初始化等。掌握默认值的各种用法能让代码更简洁、灵活且可维护。
1. 函数参数的默认值
最常用的默认值场景,允许调用函数时省略某些参数。
1.1 规则与用法
- 声明时指定默认值(通常在头文件中):
void greet(const std::string& name = "User");
- 默认参数必须从右向左连续定义:
void foo(int a, int b = 10, int c = 20); // 正确 ✅ void bar(int a = 1, int b, int c = 2); // 错误 ❌(必须从右向左连续)
示例
#include <iostream>// 函数声明:第二个参数有默认值"World"
void greet(const std::string& name = "World") {std::cout << "Hello, " << name << "!" << std::endl;
}int main() {greet(); // 输出:Hello, World!(使用默认值)greet("Alice"); // 输出:Hello, Alice!(显式提供值)return 0;
}
1.2 函数重载
默认参数可以简化某些重载情况。
示例
void print(int x) { std::cout << x; }
void print(int x, int y = 0) { std::cout << x << " " << y; }// 调用:
print(5); // 优先匹配 print(int x, int y = 0)
print(5, 10); // 匹配第二个
⚠ 注意:如果默认参数和重载有二义性,编译器会报错:
void foo(int x = 0);
void foo();
foo(); // 错误!不知道调用哪个
1.3 注意事项
- 默认值在声明中指定:通常在函数声明(头文件)中指定默认值,定义时不需要重复(否则可能冲突)。
- 从右向左设置默认值:只能从右到左连续设置默认参数,不能跳过中间的参数。
- 避免与函数重载冲突:使用默认值可以函数重载。
greet("Alice", "Hi"); // 错误,如果想用第二个参数的默认值,不能直接跳过第一个
2. 类成员变量的默认值
- 可以在类定义中为成员变量提供默认值(C++11起支持)。
- 构造函数会优先使用初始化列表的值(如果提供了的话),否则用默认值。
三种初始化方式
方式 | 写法 | 特点 |
---|---|---|
默认成员初始化(C++11) | int x = 5; | 适用于所有构造(无参数时) |
大括号初始化(C++11) | int x{}; | 零初始化(0 /nullptr ) |
构造时初始化 | int x(5); | 显式指定初始值 |
示例
class Person {
private:std::string name = "Anonymous"; // 默认初始化int age{}; // 默认0(大括号初始化)public:Person() {} // 若未显式初始化,name="Anonymous", age=0Person(std::string n, int a) : name(n), age(a) {} // 覆盖默认值
};
3. 模板参数的默认值
模板类或模板函数也可以为类型参数指定默认值(类似函数默认参数)。
示例
template <typename T = int, int Size = 10> // 默认类型=int,默认大小=10
class Array {
private:T data[Size];
};int main() {Array<> arr1; // 使用默认值:Array<int, 10>Array<double, 5> arr2; // 显式指定:Array<double, 5>return 0;
}
4. 函数对象(仿函数)的默认值
可以为函数对象的重载operator()
指定默认参数。
示例
struct Adder {int operator()(int a, int b = 10) {return a + b;}
};int main() {Adder add;std::cout << add(5); // 输出15 (5 + 默认10)std::cout << add(5, 3); // 输出8 (5 + 3)return 0;
}
5. 总结
用途 | 适用场景 | 语法例子 |
---|---|---|
函数参数 | 简化调用,提供可选参数 | void foo(int a = 0); |
类成员变量 | C++11+默认初始化 | int x = 5; / int y{}; |
模板参数 | 指定默认类型/非类型参数 | template <typename T = int> |
函数对象 | 自定义默认参数 | int operator()(int a = 0); |
默认初始化 | 避免未定义行为 | int x{}; → x=0 |
🚫 注意事项:
- 默认参数不能跳过中间的参数(必须从右向左)。
- 默认值和函数重载必须避免二义性。
- 全局变量默认初始化为零,局部变量未定义。
二、 常量成员
在C++中,常量成员(const member)是一种特殊的类成员,它可以分为两类:常量成员变量和常量成员函数。理解和使用好常量成员对于编写健壮、安全的C++代码非常重要。
常量成员
-
常量成员变量:由const修饰的成员变量,其值不可修改。
const 类型 成员变量名
-
常量成员函数:由const修饰的成员函数,其内不可修改对象成员变量的值。
返回类型 函数名() const {}
1. 常量成员变量
1.1 基本概念
常量成员变量是指在类内部声明的、值不能被修改的成员变量。
1.2 声明方式
class MyClass {
public:const int constVar; // 常量成员变量
};
1.3 初始化方法
常量成员变量有两种初始化方法:
- 默认值:可以使用默认值对类的const成员进行初始化。
- 构造函数初始化列表:必须在构造函数初始化列表中进行初始化,不能在构造函数体内赋值。
const 成员变量若没有默认值,就必须通过构造函数初始化。一旦初始化后,便不能再修改。
示例:
class MyClass {
public:const int constVar1;const int constVar2 = 3; // 默认值初始化MyClass(int value) : constVar1(value) { // 正确:在初始化列表中初始化// constVar = value; // 错误:不能在构造函数体内赋值}// 拥有默认值的常量成员,亦可在初始化列表中初始化MyClass(int value1, int value2) : constVar1(value1), constVar2(value2) {}
};
1.4 特点
- 一旦初始化后,值不可改变
- 必须在构造函数初始化列表中初始化
- 通过构造函数初始化,不同对象的常量成员变量可以拥有不同的值。
1.5 使用场景
用在每个对象都拥有各自不同的不可修改的属性值,这个属性值在对象创建的时候就被确定。
- 用于表示对象生命周期内不会改变的属性
- 提高代码安全性,防止误修改
2. 常量成员函数
常量成员函数是一种不会修改对象状态的函数。在常量成员函数体内不允许修改成员变量。但C++也提供了例外,允许在常量成员函数中修改被 mutable
修饰的成员变量
2.1 基本概念
常量成员函数是指在函数声明和定义后加const关键字的成员函数。
2.2 声明方式
class MyClass {
public:void normalFunc(); // 普通成员函数void constFunc() const; // 常量成员函数
};
2.3 定义
常量成员函数用于保证该函数不会修改对象的状态。当你有一个const对象或对象的引用/指针是const时,只能调用const成员函数。
若类未提供足够的 const 函数,const对象的可用操作会受限。
示例
class MyClass {
public:int value;void setValue(int v) { value = v; }int getValue() const { return value; } // const成员函数
};void func() {const MyClass obj;// obj.setValue(5); // 错误:const对象不能调用非const成员函数int v = obj.getValue(); // 正确:可以调用const成员函数
}
2.4 特点
- 常量成员函数不能修改类的任何普通成员变量
- 常量成员函数可以调用其他常量成员函数
- 常量成员函数不能调用非const成员函数
- const对象只能调用const成员函数,不能调用非 const 函数。
2.5 重载规则
C++允许基于const的重载,即可以同时有const和非const版本的同一个函数。
class MyClass {
public:const int& getRef() const { return value; }int& getRef() { return value; } // 重载版本的普通成员函数int value;
};
2.6 修改mutable修饰成员变量
如果确实需要在const成员函数中修改某些成员变量,可以将其声明为mutable。
class MyClass {
public:mutable int counter; // 即使在const成员函数中也可以修改void increment() const {counter++; // 允许修改mutable成员}
};
3. 总结
- 常量成员变量可以指定默认值,否则必须在构造函数的初始化列表中初始化,且初始化后不可修改。
- 常量成员函数保证不修改类成员,只能被const对象调用。
- 合理使用常量成员可以提高代码的安全性和可读性。
- mutable关键字提供了在const成员函数中修改特定成员变量的方法。
4. 综合示例
#include <iostream>
using namespace std;class Demo{
private:// 常量成员变量const int a = 9; // 常量成员变量默认值int b;mutable int c = 4; // 成员变量默认值
public:Demo(){} // 常量成员变量有默认值,构造函数可以不对其进行初始化Demo(int a, int b):a(a),b(b){} // 常量成员变量只能在初始化列表中初始化
// Demo(int a){this->a = a;} // 错误void setB(int b){
// a = 4; // 错误,成员函数中不可修改常量成员的值this->b = b;}// 常量成员函数void print()const{cout << "a = " << a << ", ";cout << "b = " << b << ", ";cout << "c = " << c << endl;
// this->b = 9; // 错误 常量成员函数中不可修改对象的值。this->c = 10; // 常量成员函数中可修改对象的mutable成员。}
};int main()
{Demo d;Demo d1(4, 7);// 不同对象拥有不同的成员常量d.setB(3);d.print();d1.print();// 常量对象只能调用const成员函数const Demo d2(5, 12);
// d2.setB(); // 错误,常量对象不能调用非const成员函数d2.print();
}