前言
以下问题以Q&A形式记录,基本上都是笔者在初学一轮后,掌握不牢或者频繁忘记的点
Q&A的形式有助于学习过程中时刻关注自己的输入与输出关系,也适合做查漏补缺和复盘。
本文对读者可以用作自查,答案在后面,需要时自行对照。
--
问题集
## 2022年6月9日 -《C++ Primer plus》第5~8章
Q1:下面语句的执行结果?(注意:逗号的优先级)
int cats = 17, 240;
int cats = (17, 240);
Q2:以下两个C++函数的定义?哪个正确?
void sum_arr(int iarray[], int n){ }void sum_arr(int[] iarray, int n){ }
Q3:手写迭代器,代码中 const int *i 是什么含义?
void print_arr(const int * begin, const int * end){for (const int *i = begin; i != end; i++) cout << *i << " ";}int main(){int arr[arrSize]= {1,2,3,4,5,6,7};print_arr(arr, arr + arrSize); //优点:从 地址begin~地址end 的遍历}
Q4:C++中,这样能修改const变量吗?
const float gmoon =1.63;
float *pm = &g_moon;
Q5:如何定义一个第一个参数接受二维数组的函数体?:int sum( ?,int size)
Q6:函数探幽——引用的本质?可以有空引用吗?
Q7:如何理解引用将两个变量 wallet1和wallet2 产生交换?
Q8: double refcube (const double &ra ); 入参 声明成这样有什么作用?
Q9:已知 double refcube (const double &ra ); 函数声明
double z = refcube (x + 3.0); 这个调用正确吗?
(注意本eg中:x是一个引用型double变量)
Q10:C++中,比较严谨的左值定义?
另外地,有没有简单粗暴的理解方法?
Q11:已知 double refcube (double &ra ); 函数声明。
什么时候编译器会让ra默认创建临时变量?(了解即可)
Q12:右值引用如何声明?一种特殊语法,使用在什么地方?
Q13:函数返回结构体引用变量的目的是什么?
Q14:将类对象传递给函数时,C++通常的做法是使用()
Q15:这个函数重载的方式正确吗?
Q16:这个函数重载的是否正确?
int func(int a);
int func(const int a);
Q17:模版函数的两种写法?
Q18:显式模板化是什么?其函数的声明?
Q19:重载匹配的规则?
Q20:cout << lesser<> (m, n) << endl ; 这个语法是什么意思?
Q21:函数模板中遇到未知变量类型,该使用何种关键字进行?
ps:这个情境最主要是因为模版无法预知入参运算的结果,比如 int+float 和 int+long 就很不同
参考解答
Q1:下面语句的执行结果?
int cats = 17, 240;
int cats = (17, 240);
A1:
Q2:以下两个C++函数的定义?哪个正确?
void sum_arr(int iarray[], int n){ }void sum_arr(int[] iarray, int n){ }
函数的入参为数组的时候,入参应被视为数组本身,还是数组首地址?
A2:void sum_arr(int iarray[], int n){ }正确;
根据C++规则,入参为数组,入参 int iarray[] = 数组名 = 函数指针 = 其第一个元素的地址
也就是说:函数传递的实则为地址。
这里补充了一个实验:
void sum_arr(int iarray[], int n){cout << 1;}void sum_arr(int * iarray, int n){cout << 2;}
在这段代码中,函数 sum_arr 并不视为两种重载,而是视为重复定义,也就是其本质一样。
void sum_arr(int iarray[ ], int n) 实际在编译时被转换成了 → void sum_arr(int * iarray, int n)
注意:在C++中,当且仅当用于函数头或函数原型中,int *arr 和 int arr []的含义才是相同的。它们都意味着 arr 是一个 int 指针。
Q3:手写迭代器,代码中 const int *i 是什么含义?
void print_arr(const int * begin, const int * end){for (const int *i = begin; i != end; i++) cout << *i << " ";}int main(){int arr[arrSize]= {1,2,3,4,5,6,7};print_arr(arr, arr + arrSize); //优点:从 地址begin~地址end 的遍历}
A3:const int *i 表示一个 指向 const int 的指针,意味着这个指针的位置不会发生改变。
Q4:这样能修改const变量吗?
const float gmoon =1.63;
float *pm = &g_moon;
A4:不可以,语法上直接报错,其正确格式:
const float gmoon = 1.63;
const float *pm = &g_moon;
C++禁止将 const 的地址赋给非 const 指针。// error: invalid conversion from 'const char*' to 'char*' [-fpermissive]
如果读者非要这样做,可以使用强制类型转换来突破这种限制,详情请参阅第15章中对运算符const_cast 的讨论,这个东西专门用来突破const界限。
Q5:如何定义一个第一个参数接受二维数组的函数体?:int sum( ?,int size)
A5:如下
ar2是指针而不是数组。还需注意的是,指针类型指出,它指向由 4 个int组成的数组。
因此,指针类型指定了列数,这就是没有将列数作为独立的函数参数进行传递的原因。
Q6:函数探幽——引用的本质?可以有空引用吗?
A6:给变量取别名;
引用变量的主要用途是用作函数的形参,引用变量用作参数,函数将使用原始数据,而不是其副本
引用要求初始化的时候就产生“绑定”,不能跳过初始化。这个特性比较类似const指针
Q7:如何理解引用将两个变量 wallet1和wallet2 产生交换?
A7:在swapr()中,变量a和b是 wallet1 和 wallet2 的别名,所以交换a和b的值相当于交换 wallet1 和 wallet2 的值;
注意:引用是别名,而不是地址。只是提供了一种方式来访问和操作它所绑定的对象。
这里有个习作,看看能不能看懂:
int man = 233; // 定义manint & robot = man; // 定义引用变量 robot,绑定man,以后robot就是man的别名了vplus(robot); // 给robot表示的变量输入vplus函数cout << man; }void vplus (int & value){ // vplus:给入参表示的变量++; 即:给value表示的robot表示的man变量++value ++; // 实际上,robot,man和vplus运行中的value指向了同一个值}
Q8: double refcube (const double &ra ); 参数 声明成这样有什么作用?
A8:声明引用变量 &ra 是不可变的引用。引用型变量如果在函数体内部变来变去,外面的值也就发生变化了。
这个功能对于 ra 是一个double而言,实际上和值传递没有区别。
这个功能最有用的地方是用来引用struct或者class,如下:
之后在其中不用指针操作,而是可以直接在函数体内写 ft.name = "Joe"
Q9:接上面,已知 double refcube (const double &ra ); 声明
double z = refcube (x + 3.0); 这个调用正确吗? (本eg:x是一个非const 的 double 引用变量)
A9:不正确,因为x+3不是一个变量。
这里涉及了一个不容易被注意的规则:如果实参(double)与引用参数(const double &)不匹配,C++将生成临时变量。(具体的规则可以看Q11)
Q10:C++中,比较严谨的左值定义?
A10:定义比较简单,主要基于是否可被赋值,int a,以及 const int a; 都可以被视作左值
较为粗暴的理解:
C++中所有值必然属于左值或者右值,
在C++11中可以取地址的、有名字的就是左值
反之,不能取地址的、没有名字的就是右值。
Q11:已知 double refcube (double &ra ); 声明
什么时候会让ra默认创建临时变量?(编译器差异,了解即可)
A11:目前,这种语法如果使用右值,已经不再是告警,而是直接报错:包括下面 c++ prime plus 书中的案例
旧版本两种情况:
1)实参的类型正确,但不是左值;
2)实参的类型不正确,但可以转换为正确的类型。
总而言之,就是需要临时整一下,不能直接提供。
Q12:右值引用如何声明?一种特殊语法,使用在什么地方?
A12:用&&声明,
int && ref = 7;
cout << ref << endl;
允许将资源(如内存、文件句柄等)从临时对象转移到新对象,实现移动语义
Q13:函数返回结构体引用变量的目的是什么?
A13:为了效率,少一次拷贝。
Q14:将类对象传递给函数时,C++通常的做法是使用()
A14:引用
Q15:这个函数重载的方式正确吗?
A15:不正确。
Q16:这个函数重载的是否正确?
int func(int a);
int func(const int a);
A16:不正确,const 不会被认作区分。
Q17:模版函数的两种写法?
A17:
template <typename T>;
或者 template <class T>; // 后者C++98老式风格
void Swap(T &a,T &b) ;
Q18:显式模板化是什么?其函数的声明?
A18:
Q19:重载匹配的规则?
A19:哪个最接近匹配哪个。默认优先级:正常函数 > 模版函数
Q20:cout << lesser<> (m, n) << endl ; 这个语法是什么意思?
A20:主要是对于有函数模板和正常函数并存的情况,手动选择函数模板。
Q21:函数模板中遇到未知变量类型,该使用何种关键字进行?
ps:这个情境最主要是因为模版无法预知入参运算的结果,比如 int+float 和 int+long 就很不同
A21:通过关键字 decltype(x) y ,make y's type same as x;这个语法有点 auto y as x 的意思;
decltype 的内在本质是一个查表逻辑
Q22:函数模板中遇到未知返回值类型,该使用何种关键字进行?
ps:比起上一个问题,这个的难度在于考虑 x和y 甚至没有声明,编译器一无所知
后置返回类型 的内在本质是让 x和y 两个入参具备了作用域前提
A22:auto func() -> TYPE,
// 这里的 TYPE 可以是传统类型,也可以是 decltype(入参)
经测验,这个代码可以运行正常(没有用decltype):
template <typename T1, typename T2>
auto add(T1 a, T2 b){ // auto add(T1 a, T2 b) -> decltype(a+b) is also correct
return a+b;
}
int main(){
int a = add(5,6);
double b = add(5, 6.2);
cout << a << endl;
cout << b << endl;
}