12-x 第十二章总结和测验

快速回顾

复合数据类型Compound data types (也称为组合数据类型composite data type)是由基本数据类型(或其他复合数据类型)构造的数据类型。

表达式的值类别value category指示表达式是解析为值、函数还是某种对象。

左值lvalue是一个表达式,其值是一个具有标识的函数或对象。具有标识identity的对象或函数具有一个标识符或一个可识别的内存地址。左值分为两种子类型:可修改左值modifiable lvalues是可以修改的左值,而不可修改左值non-modifiable lvalues 是其值不能修改的左值(通常是因为它们是 const 或 constexpr)。

右值rvalue是指非左值的表达式。这包括字面量(字符串字面量除外)以及函数或运算符的返回值(当作为值返回时)。

引用reference是现有对象的别名。一旦定义了引用,对该引用执行的任何操作都会应用于被引用的对象。C++ 包含两种类型的引用:左值引用和右值引用。左值引用lvalue reference(通常简称为引用reference )充当现有左值(例如变量)的别名。左值引用变量lvalue reference variable是一个指向左值(通常是另一个变量)的引用变量。

当一个引用被初始化为一个对象(或函数)时,我们称它绑定bound到该对象(或函数)。被引用的对象(或函数)有时被称为被引用项referent

左值引用不能绑定到不可修改的左值或右值(否则就可以通过引用更改这些值,这将违反它们的常量性)。因此,左值引用有时被称为指向非常量值的左值引用lvalue references to non-const(有时简称为非常量引用non-const reference)。

C++ 中的引用一旦初始化就不能重新定位reseated,这意味着它不能被更改为引用另一个对象。

当被引用的对象在指向它的引用被销毁之前就被销毁时,该引用就指向了一个已经不存在的对象。这样的引用被称为悬空引用dangling reference。访问悬空引用会导致未定义行为。

在声明左值引用时使用关键字const,我们告诉左值引用将其引用的对象视为常量。这样的引用称为指向常量值的左值引用lvalue reference to a const value(有时也称为指向常量的引用reference to const 常量引用const reference)。常量引用可以绑定到可修改的左值、不可修改的左值和右值。

临时对象temporary object(有时也称为未命名对象unnamed object 匿名对象anonymous object)是在单个表达式中创建用于临时使用(然后销毁)的对象。

使用引用传递pass by reference时,我们将函数参数声明为引用(或常量引用),而不是普通变量。函数调用时,每个引用参数都会绑定到相应的实参。由于引用充当实参的别名,因此不会创建实参的副本。

取地址运算符address-of operator(&) 返回其操作数的内存地址。解引用运算符dereference operator(*) 返回给定内存地址处的值,并将其作为左值返回。

指针pointer是一个对象,它的值是一个内存地址(通常是另一个变量的地址)。这允许我们存储其他对象的地址以供后续使用。与普通变量一样,指针默认情况下不会被初始化。未初始化的指针有时被称为“野指针wild pointer”。悬空指针dangling pointer是指指向已失效对象(例如已被销毁的对象)地址的指针。

除了内存地址之外,指针还可以保存一个额外的值:空值。空值null value(通常简写为null)是一个特殊值,表示某个东西没有实际值。当指针保存空值时,意味着该指针没有指向任何内容。这样的指针称为空指针null pointer**nullptr**关键字表示空指针字面量。我们可以使用nullptr它来显式地初始化或将指针赋值为空值。

指针要么应该保存有效对象的地址,要么应该被设置为空nullptr。这样我们就只需要检查指针是否为空,并且可以假设任何非空指针都是有效的。

指向常量值的指针pointer to a const value (有时简称为指向常量的指针pointer to const )是指向常量值的(非常量)指针。

常量指针const pointer是指初始化后地址不能改变的指针。

指向常量值的常量指针const pointer to a const value的地址不能改变,它所指向的值也不能通过该指针改变。

使用按地址传递pass by address时,调用者不是提供对象本身作为参数,而是提供对象的地址(通过指针)。这个指针(保存着对象的地址)会被复制到被调用函数的指针参数中(该参数现在也保存着对象的地址)。然后,函数可以通过解引用该指针来访问被传递地址的对象。

按引用返回Return by reference 会返回一个指向被返回对象的引用,这样就避免了复制返回值。使用按引用返回有一个主要缺点:程序员必须确保被引用的对象在返回该引用的函数执行完毕后仍然存活。否则,返回的引用会变成悬空引用(指向一个已被销毁的对象),使用该引用会导致未定义行为。如果一个参数是通过引用传递给函数的,那么也可以安全地通过引用返回该参数。

如果一个函数返回一个引用,并且该引用用于初始化或赋值给一个非引用变量,则返回值将被复制(就像它是按值返回的一样)。

通过关键字auto进行变量类型推导时,会从被推导的类型中移除所有引用或顶层常量限定符。如果需要,可以在变量声明中重新应用这些限定符。

按地址返回Return by address 与按引用返回几乎完全相同,区别在于按地址返回的是指向对象的指针,而不是指向对象的引用。


测验时间

问题 1

对于运算符 << 右侧的每个表达式,请指出该表达式是左值还是右值:

a)

std::cout << 5;

显示解决方案

字面量是右值,所以5也是右值

b)

int x { 5 };
std::cout << x;

显示解决方案

该表达式x标识变量x,因此该表达式是左值。

c)

int x { 5 };
std::cout << x + 1;

显示解决方案

该表达式x + 1计算的是一个临时值,因此该表达式是一个右值。

d)

int foo() { return 5; }
std::cout << foo();

显示解决方案

函数的返回值(当通过值返回时)是右值。

e)

int& max(int &x, int &y) { return x > y ? x : y; }
int x { 5 };
int y { 6 };
std::cout << max(x, y);

显示解决方案

max() 返回一个左值引用,该引用是一个左值。

问题 2

这个程序的输出结果是什么?

#include <iostream>

int main()
{
	int x{ 4 };
	int y{ 6 };

	int& ref{ x };
	++ref;
	ref = y;
	++ref;

	std::cout << x << ' ' << y;

	return 0;
}

显示解决方案

7 6

请记住,引用不能重新排列,因此ref = y不会ref引用y。它等价于x = y。
附有解释性评论:

#include <iostream>

int main()
{
	int x{ 4 };
	int y{ 6 };

	int& ref{ x }; // ref is now an alias for `x`
	++ref;   // x is now 5
	ref = y; // x is now 6
	++ref;   // x is now 7

	std::cout << x << ' ' << y; // prints 7 6

	return 0;
}

问题 3

请列举两个我们尽可能使用常量引用而不是非常量引用来传递参数的原因。

显示解决方案

非常量引用参数可以用来修改参数的值。如果不需要这种功能,最好使用常量引用传递参数,以免意外修改参数。
非常量引用参数只能接受可修改的左值作为参数。常量引用参数可以接受可修改的左值、不可修改的左值或右值作为参数。

问题 4

常量指针和指向常量的指针有什么区别?

显示解决方案

常量指针是指其地址不可更改的指针(不能重新指向其他对象)。但是,它指向的对象的值可以更改。
指向常量的指针是指指向一个对象的指针,该对象的值不能通过该指针本身进行更改。但是,该指针可以重新指向另一个对象。

问题 5

编写一个名为 sort2 的函数,它允许调用者传递 2 个 int 变量作为参数。当函数返回时,第一个参数应保存两个值中的较小者,第二个参数应保存两个值中的较大者。

提示:std::swap()可以使用函数(位于 标头中)交换两个变量的值。例如,std::swap(x, y)交换变量x和的值y。

以下代码应该可以运行并打印出注释中提到的值:

#include <iostream>

int main()
{
    int x { 7 };
    int y { 5 };

    std::cout << x << ' ' << y << '\n'; // should print 7 5

    sort2(x, y); // make sure sort works when values need to be swapped
    std::cout << x << ' ' << y << '\n'; // should print 5 7

    sort2(x, y); // make sure sort works when values don't need to be swapped
    std::cout << x << ' ' << y << '\n'; // should print 5 7

    return 0;
}

显示解决方案

#include <algorithm> // for std::swap
#include <iostream>

void sort2(int& lesser, int& greater)
{
    // If the values are not sorted, we need to swap them so they are
    if (lesser > greater)
        std::swap(lesser, greater);

    // If we wrote our own swap instead, we could do so like this:
    // int swap { lesser };
    // lesser = greater;
    // greater = swap;

}

int main()
{
    int x { 7 };
    int y { 5 };

    std::cout << x << ' ' << y << '\n';

    sort2(x, y); // make sure sort works when values need to be swapped
    std::cout << x << ' ' << y << '\n';

    sort2(x, y); // make sure sort works when values don't need to be swapped
    std::cout << x << ' ' << y << '\n';

    return 0;
}
posted @ 2025-12-19 17:32  游翔  阅读(19)  评论(0)    收藏  举报