前言
以下问题以Q&A形式记录,基本上都是笔者在初学一轮后,掌握不牢或者频繁忘记的点
Q&A的形式有助于学习过程中时刻关注自己的输入与输出关系,也适合做查漏补缺和复盘。
本文对读者可以用作自查,答案在后面,需要时自行对照。
--
问题集
Q1:这组程序中,两个static变量哪个是长期在内存的?
Q2:C++中,内部链接性和外部链接性有什么区别?
Q3:#pragma once 以及 #ifndef ... #define xx_H_ ... #endif 的作用?
Q4:结构体和class的区别?
Q5:拷贝构造函数是干什么用的?定义的固定格式?(入参?)
Q6:以下程序中哪些试图调用构造函数的方式是正确的?
Q7:以下程序调用几次拷贝构造?
void callPerson(Person p){
return ;
}
int main(){
Person p1;
callPerson(p1);
return 1;
}
Q8:以下程序调用几次拷贝构造?(区别是返回类型是Person)
Person callPerson(Person p){
return p;
}
int main(){
Person p1;
callPerson(p1);
return 1;
}
Q9:构造函数调用规则如下:
如果用户定义有参构造函数,则不在提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,c++默认不会再提供其他构造函数;
问题:如果我 Person p1 = p2,这个时候除了会调用拷贝构造函数,还会调用其他构造函数吗?(假设有定义~)
Q10:浅拷贝和深拷贝的定义?编译器默认提供的拷贝构造函数是什么样子的?
Q11:浅拷贝的bug是什么?
Q12:深拷贝的具体操作?
Q13:这个构造函数的语法?
class Person
{
public:
int age;
string name;
Person():age(10),name("default") {cout<<"gz"<<endl;}
...
}
Q14:this指针的用法之一是防止冲突,还有什么作用?
Q15:这个类内成员函数的语法?
Q16:const Person p; 这个p能否调用
Q17:想想链表中的有参构造会怎么做?如何优雅的写出来带默认初始值的构造函数?
Q18:C++中支不支持类中类?
Q19:友元中的friend可以这么用吗?
int main(){
friend p = Person();
return 0;
}
Q20:友元的两个比较常用的操作
Q21:类外定义函数?可以定义构造函数吗?
Q22:运算符重载的本质?
Q23:运算符重载的两种方式?类内重载&全局重载?为什么要学习全局重载?
Q24:这个重载<<的操作正确吗?为什么要返回 ostream& 类型?
// 类外定义<<重载
ostream& Person::operator<<(ostream &out, const Person &p2){
}
参考解答
Q1:这组程序中,两个static变量哪个是长期在内存的?
A1:都在,而且全局外的 int global 和这俩,都是长期在内存中的。即使不调用 funct1(),具体参考下表
Q2:C++中,内部链接性和外部链接性有什么区别?
A2:在C++中,链接性(Linkage)指的是变量、函数或模板等实体在不同编译单元(Translation Unit)之间的可见性和可用性。
1)外部链接性(External Linkage,如 int global )
具有外部链接性的实体可以在多个编译单元中被定义和使用。(多个文件中共享编译)
这些实体在编译后生成的可执行文件或库文件中具有全局唯一性。
例如,如果你在一个编译单元中声明了一个具有外部链接性的全局变量或函数,然后在另一个编译单元中引用它,编译器会链接到同一个全局实体。
通常,全局变量和函数默认具有外部链接性,除非它们被声明为static。
2)内部链接性(Internal Linkage,如 static int global)
具有内部链接性的实体仅在其定义的编译单元内部可见。(仅当前cpp)
这意味着即使在其他编译单元中声明了相同名称的实体,它们也会被视为不同的实体。
内部链接性可以通过在全局变量或函数前加上static关键字来实现,这会限制其作用域仅在定义它的编译单元内。
这有助于避免命名冲突,并可以用于实现编译单元内私有的全局变量或函数。
Q3:#pragma once 以及 #ifndef ... #define xx_H_ ... #endif
这两套东西是用来做什么的?
A3:#pragma once 和 #ifndef ... #define ... #endif 都是用来防止头文件被多次包含的机制
1. #pragma once 是一种非标准的、但广泛支持的预处理指令,用于指示编译器只包含一次头文件。当编译器遇到 #pragma once 时,它会确保该文件在整个编译单元中只被包含一次。这种方式简单且有效,但因为它不是C/C++标准的一部分,所以可能在某些编译器上不被支持。
2. #ifndef ... #define ... #endif 是一种标准的防止头文件重复包含的方法,通过宏定义来实现。具体步骤如下:
- `#ifndef` 检查是否已经定义了某个宏(通常是头文件的名称,后缀 `_H_` 或 `_H` 是一种常见习惯)。
- `#define` 如果没有定义,则定义该宏。
- `#endif` 结束条件编译块。
// example.h
#ifndef EXAMPLE_H_ // 如果没有定义 EXAMPLE_H_
#define EXAMPLE_H_ // 定义 EXAMPLE_H_
// 头文件内容
#endif // EXAMPLE_H_
第二种方式是标准的,可以在所有支持C/C++的编译器上工作。而 `#pragma once` 虽然在大多数现代编译器上都有效,但因为它不是标准的一部分,所以可能在某些特定情况下不被支持。在编写可移植性要求较高的代码时,推荐使用 `#ifndef ... #define ... #endif` 的方式。
对象和类
Q4:结构体和class的区别?
A4:OOP中,主要是权限。struct 一个 a对象 和 class b对象
a.属性 默认是public的,直接可以访问。
b.属性 默认是private的,不可以直接访问。
Q5:拷贝构造函数是干什么用的?定义的固定格式?(入参?)
A5:就是 class Person中,让p1 = p2的。
这样在初始化的时候,p1就可以顺承所有p2的基本信息。
另外需要注意,使用场景可以是
1)值传递一个入参为 class Person p 的函数,此时会调用一次拷贝构造函数。(传参嘛,肯定要拷贝一个副本)
2)return返回如果是 class Person p 的话,
Q6:以下程序中哪些试图调用构造函数的方式是正确的?
A6:除了第二行都正确,第二行是会被编译器认为是声明了一个返回值为Person,没有参数的函数
这里有个比较重要的点:C#和C++很不一样的一个地方
C++ 中 Person p1 就会调用构造函数
C# 中,只有new的方法才会调用构造函数
Q7:以下程序调用几次拷贝构造?
void callPerson(Person p){
return ;
}
int main(){
Person p1;
callPerson(p1);
return 1;
}
A7:一次,原因是p1值传递,建立一个临时变量用来存储p1的副本
Q8:以下程序调用几次拷贝构造?(区别是返回类型是Person)
Person callPerson(Person p){
return p;
}
int main(){
Person p1;
callPerson(p1);
return 1;
}
A8:两次,在这段代码中,
当main函数调用callPerson函数时,p1作为参数传递。建立一个临时变量用来存储p1的副本,这是拷贝构造函数第一次被调用。
由于返回类型是Person,编译器会创建一个新的Person对象,这个对象是p的拷贝,然后将其作为函数的返回值。这是拷贝构造函数第二次被调用。
所以,总共调用了两次拷贝构造函数。第一次是在调用callPerson时,第二次是在callPerson返回时。
Q9:构造函数调用规则如下:
如果用户定义有参构造函数,则不在提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,c++不会再提供其他构造函数;
问题:如果我 Person p1 = p2,这个时候会调用拷贝构造函数,还会调用构造函数吗?(假设有定义~
A9:不会了。实践出真知。可能就是没必要吧
Q10:浅拷贝和深拷贝的定义?编译器默认提供的拷贝构造函数是什么样子的?
A10:浅拷贝:赋值,即a = b; 深拷贝:在堆区重新申请空间,进行赋值操作;
编译器默认提供的拷贝构造函数是浅拷贝,也就是说我们什么都不写,会有 m_data = data 的操作。
Q11:浅拷贝容易的bug是什么?
A11:浅拷贝最容易遇到的问题是容易造成指针所指向资源的重复释放。
两个解决方法 1)浅拷贝用 !=NULL 避免二次释放 2)采用深拷贝
Q12:深拷贝的具体操作?
A12:
Q13:这个构造函数的语法?
class Person
{
public:
int age;
string name;
Person():age(10),name("default") {cout<<"gz"<<endl;}
...
}
A13:初始化列表,比较常用
Q14:this指针的用法之一是防止冲突,还有什么作用?
A14:this指针始终指向调用成员方法的对象本身。其本身是一个const class *p
除了 this->age = age 的使用以外,还有定义成员方法时,通过返回 Person & 的方法 使用 return *this
这种情况下可以有 func().func().func(). 这种链式调用。
注意:为什么成员函数的返回类型 一定要是 Person & 而不是Person ?
原因是:Person func() 的方式 return的是对象的拷贝副本,而不是对象本身。如果没有return获取到对象本体,那么程序逻辑就会出现错误。
附:一个自己写的可以被链式调用的LinkList方法(虽然有瑕疵,他并不安全hh):
LinkNode & AddBackNode(LinkNode &target){ // unsafe
LinkNode *src = this;
target.next = src->next;
src->next = ⌖
return target;
}
Q15:这个类内成员函数的语法?
A15:常函数,使用这个语法之后 this->属性 就不再能够改变。看着不是很优雅,但是const实在没更合适的地方加了
常函数的语法只能在class中使用。
常函数的主要含义是:这个函数将不能改变class中的任何成员属性。
Q16:const Person p; 这个p能否调用 Person 类中一个修改了成员属性的函数?
进一步地,能否调用 Person 类中,一个访问了成员属性的朴素函数?
A16:均不可以。常对象只能调用常函数(见Q15)
Q17:想想链表中的有参构造会怎么做?如何优雅的写出来带默认初始值的构造函数?
A17:这个东西学名叫做初始化列表
LinkNode() : val(0), next(nullptr){}
LinkNode(int x) : val(x), next(nullptr){}
Q18:C++中支不支持类中类?
A18:支持。比如刷算法题时,我们会在定义链表LinkList的时候,从中建立一个LinkNode类
这样可以把对于链表的管理(如:dummyHead和size)和节点的管理(val和next)分离开。
Q19:友元中的friend可以这么用吗?
int main(){
friend p = Person();
return 0;
}
A19:显然不行,friend只能在类内声明
Q20:友元的两个比较常用的操作:
1)全局函数做友元:先在原来类中声明friend,再编写全局函数,这个全局函数就可以访问到private属性
2)友元类:这个类内部的成员方法可以访问private属性
Q21:类外定义函数?可以定义构造函数吗?
A21:可以,就是要表明是哪个类(这个 Person:: 学名作用域)
class Person{
Person(); //这里是声明
public:
int score;
};
// 类外定义构造函数
Person::Person():score(0){}
Q22:运算符重载的本质?
A22:运算符实际上是C++为编程者预设了一个合乎规范的函数名(operator+;operator<< 等)
以下两个格式的调用语句等价:
p3 = p1 + p2;
p3 = p1.operator+(p2);
Q23:运算符重载的两种方式?类内重载&全局重载?为什么要学习全局重载?
A23:我们假设有Person类,首先,两种重载的方式都支持 p3 = p1 + p2 的最终写法
全局重载:有些时候无法在类内达成重载的效果,比如重载 "<<",我们写在类内,只能达到 P<<... 的效果
而对于 cout << P,就不能完成了,必须通过全局函数对运算符进行重载(见Q24)
Q24:这个重载<<的操作正确吗?为什么要返回 ostream& 类型?
// 类外定义<<重载
ostream& Person::operator<<(ostream &out, const Person &p2){
}
A24:不对,这样会报错 Person类中没有成员 operator<<,定义的格式错了,正确格式应该是:
ostream& operator<<(ostream &out, Person &p){
out << p.score << endl; // 选择我们想要给流输出的内容
return out;
}
返回类型:ostream& 这个返回的类型确保我们可以 cout << P << endl; 这也是一种链式调用的设计
入参:分别是 ostream &out, Person &p,只有这样的一前一后,才能对应 “cout << P” 的格式。
Q25:可以被重载的运算符大概有哪些?了解即可
A25: