应尽可能的使用const
-
使用const可以避免无意中修改数据的编程错误;
-
使用const使函数能够处理const和非const实参,否则只能接收非const数据(前提:函数参数为引用 / 指针);
-
使用const引用使函数能够正确生成并使用生成的临时变量。
将引用用于结构
结构 / 类的对象往往体积较大,值传递会触发浅拷贝 和深拷贝。
-
浅拷贝:仅复制成员变量的表面值(如指针)。可能导致内存被多次释放( C++ 默认)。
-
深拷贝:会递归复制原始对象及其所有嵌套对象,生成一个完全独立的新对象。对内存的开销大(需要手动编写拷贝构造函数实现)。
指针操作繁琐可读性差,容易产生野指针,比较危险。
引用共用空间,无拷贝;语法简洁,可读性高;无空野指针,安全。
引用函数(和指针函数类似)
//语法:数据类型& 函数名(形参列表);
引用可以作为函数返回值,可以避免拷贝,直接操作对象。
// 结构体定义(投篮数据)
struct free_throws {
string name;
int made;
int attempts;
};
//引用函数,用于更新数据
free_throws& accumulate(free_throws& target, const free_throws& source) {
// 累加投中次数和尝试次数
target.attempts += source.attempts;
target.made += source.made;
return target;
}
...
// 初始化三个球员的数据
free_throws player1 = {"库里", 45, 50,};
free_throws player2 = {"汤普森", 38, 40};
free_throws team_total = {"勇士队合计", 0,0};
// 链式调用:将 player1 和 player2 的数据累加到 team_total
accumulate(accumulate(team_total, player1), player2);
//也可以这样用
accumulate(team_total, player1).attempts = 3;
accumulate(accumulate(team_total, player1), player2);
**引用函数可以实现链式操作。**使用无返回值函数可以操作同一片空间,但无法实现链式操作。使用值返回函数,编译器会创建临时变量,操作的不是同一片空间,不仅无效,而且创建副本对内存开销很大。
accumulate(team_total, player1).attempts = 3;
引用函数的返回值可以做左值,因为其和原数据共享空间,空间固定。倘若是值传递,由于是临时数据,编译器会报错。
将引用用于类对象
将类对象传递给函数时,C++通常的做法是使用引用。
C++里面定义了一种char*到string的转换功能,所以可以使用C风格的字符串来初始化string对象。
在传参时,引用参数是 const 类型,实参类型不正确,且可以隐式转换为形参类型,会创建临时对象,使用的是临时空间。
注:千万不要返回局部的引用,返回时引用已被销毁,执意使用编译器会警告不会报错,但程序试图执行这个引用函数的时候,程序会崩溃。
对象、继承和引用
对象是类的实例,可以通过成员函数操作。
继承是能够将特性从一个类传递到另一个类的语言特性。 派生类(子)会继承基类(父)的方法,比如基类有 name,派生类不用再定义,直接能用;
基类的引用可以指向派生类的对象。而且无需强制类型转换。
...
ofstream fout; // 1. 创建派生类对象:ofstream是ostream的派生类,用于向文件写数据
const char * fn = "ep-data.txt"; // 要写入的文件名(C风格字符串)
fout.open(fn); // 2. 打开文件:关联fout对象和"ep-data.txt"文件
double objective; // 存储望远镜物镜的焦距(单位:mm)
// 提示用户输入物镜焦距
cout << "请输入你的望远镜物镜焦距(单位:mm):";
cin >> objective; // 读取用户输入的物镜焦距
// ostream& os(基类引用)直接绑定fout,无需强转 → 数据写入文件
file_it(fout, objective);
cout << "数据已写入文件!" << endl;
fout.close();//关闭文件
...
// os:基类引用,绑定派生类对象(fout或cout),实现通用输出
void file_it(ostream & os, double fo)
{
ios_base::fmtflags initial; // 存储输出流的“初始格式状态”(用于后续恢复)
initial = os.setf(ios_base::fixed); // 设置输出为“固定小数格式”,并保存初始格式
os.precision(0); // 设置小数位数为0(整数输出)
os << "物镜焦距:" << fo << " mm\n"; // 输出物镜焦距(os绑定fout则写文件,绑定cout则写屏幕)
os.setf(ios::showpoint); // 强制显示小数点(即使是整数也显示 .0)
os.precision(1); // 设置小数位数为1(目镜焦距保留1位小数)
os.width(12); // 设置下一个输出内容的宽度为12字符(右对齐,默认填充空格)
os << "目镜焦距(mm)"; // 输出表头1
os.setf(initial); // 恢复输出流的初始格式(避免影响后续其他输出)
}
ofstream 和 cout拥有 这些格式控制功能,os 作为基类引用,能直接调用这些继承来的功能。简单来讲就是:辈分高的类(父类)的引用/指针,可以接收(例如形参接收实参)辈分低的类(子类)的对象,接收后,这个子类对象可以使用父类的方法,但不能使用子类特有的方法。子类的引用/指针也不能接收父类的对象。
何时使用引用参数
-
大对象,值传递会创建一个完整的副本,如果参数是体积大的对象,对内存的开销很大。
-
函数需要修改实参的值时,使用指针可读性低而且不够安全。
const修饰---------------------------------------------------------------------------------
- 大对象,可以使用const指针或const引用。
- 如果传递对象是类对象,使用const引用,类设计常常要求使用引用。
何时使用值传递
-
传小对象。
-
如果对象是数组,则使用指针,这是唯一选择,并将指针用const修饰(使用const修饰是为了防止函数内部意外修改数组的内容(保护实参数组)。