一.struct和class的区别
1、struct访问权限默认是public,class访问权限默认是private.这个原因是struct原本是C的关键字,C没有访问权限,所有的都是公有的,C++兼容C,必须让struct默认也是public.
2、还有一个区别:"class"这个关键字还用于定义模板参数,就像"typename"。但关键字"struct"不用于定义模板参数
二.函数的重载,覆盖,隐藏
1.重载:是在同一个类里实现的,函数名相同,参数不同(包括参数类型,个数),virtual可有可无。
2.覆盖:不在同一个类,如:基类和派生类中都有相同函数名,参数相同,基类函数有virtual关键字,派生类函数会覆盖基类函数。
3.隐藏:是派生类函数覆盖了基类函数,
*要点 :(1).派生类函数与基类函数同名,但参数不同。此时,不论有无virtual关键字,基类都将会被覆盖。
(2).派生类函数与基类函数同名,并且参数相同,但基类没有virtual关键字。就会隐藏。如果基类有的话就是,覆盖了,多态。
三.堆和栈的区别
1.栈:由系统分配和回收,比如定义一个局部变量 int a;系统为a在栈中分配空间。当函数结束时系统自动销毁a并回收内存.栈一般比较小(1~10M)
2.堆:是由程序员分配和回收,如:malloc一个空间或者new一个空间,用完后使用free或者delete释放,不释放会出现内存泄漏,堆一般比较大(超过1G)。
堆和栈的区别可以用如下的比喻来看出:使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大
四.C++ 中设计不能被继承的类
1.方法一:使用私有构造函数和友元类
在 C++ 里,可通过将构造函数设为私有,再利用一个友元类来创建该类的对象,这样其他类就无法继承这个类。
cpp
#include <iostream>
// 定义一个不能被继承的类
template <typename T>
class FinalBase {
friend T;
private:
FinalBase() {
std::cout << "FinalBase constructor" << std::endl;
}
~FinalBase() {
std::cout << "FinalBase destructor" << std::endl;
}
};
class FinalClass : virtual public FinalBase<FinalClass> {
public:
FinalClass() {
std::cout << "FinalClass constructor" << std::endl;
}
~FinalClass() {
std::cout << "FinalClass destructor" << std::endl;
}
};
// 以下尝试继承会导致编译错误
// class Derived : public FinalClass {
// public:
// Derived() {}
// };
int main() {
FinalClass obj;
return 0;
}
代码解释:
FinalBase
类把构造函数和析构函数设为私有,这样外部类无法直接实例化FinalBase
。FinalBase
类采用模板,把FinalClass
作为友元类,这样FinalClass
就能访问FinalBase
的私有成员。FinalClass
虚继承自FinalBase<FinalClass>
,保证FinalBase
只被初始化一次。- 若尝试定义一个继承自
FinalClass
的类,由于无法访问FinalBase
的私有构造函数,会导致编译错误。
2.方法二:使用 final
关键字(C++11 及以后)
C++11 引入了 final
关键字,能直接将类标记为不能被继承。
cpp
#include <iostream>
// 使用 final 关键字声明不能被继承的类
class FinalClass final {
public:
FinalClass() {
std::cout << "FinalClass constructor" << std::endl;
}
~FinalClass() {
std::cout << "FinalClass destructor" << std::endl;
}
};
// 以下尝试继承会导致编译错误
// class Derived : public FinalClass {
// public:
// Derived() {}
// };
int main() {
FinalClass obj;
return 0;
}
代码解释:
FinalClass
类使用final
关键字修饰,表明该类不能被继承。- 若尝试定义一个继承自
FinalClass
的类,编译器会报错。
五.容器适配器
1. std::stack
(栈)
- 特点:遵循后进先出(LIFO,Last In First Out)的原则,就像一摞盘子,最后放上去的盘子总是最先被拿走。
- 底层容器 :默认使用
std::deque
作为底层容器,也可以指定使用std::vector
或std::list
。 - 常用操作 :
push()
:向栈顶添加元素。pop()
:移除栈顶元素。top()
:返回栈顶元素的引用。empty()
:判断栈是否为空。size()
:返回栈中元素的数量。
2. std::queue
(队列)
- 特点:遵循先进先出(FIFO,First In First Out)的原则,类似于排队,先到的人先接受服务。
- 底层容器 :默认使用
std::deque
作为底层容器,也可以指定使用std::list
。 - 常用操作 :
push()
:向队列尾部添加元素。pop()
:移除队列头部元素。front()
:返回队列头部元素的引用。back()
:返回队列尾部元素的引用。empty()
:判断队列是否为空。size()
:返回队列中元素的数量。
3. std::priority_queue
(优先队列)
- 特点 :元素按照优先级进行排序,优先级高的元素总是先被取出。默认情况下,使用
<
运算符来确定元素的优先级,即较大的元素优先级较高。 - 底层容器 :默认使用
std::vector
作为底层容器,并且使用堆(Heap)数据结构来维护元素的优先级。 - 常用操作 :
push()
:向优先队列中添加元素,添加后会自动调整堆结构以保持优先级顺序。pop()
:移除优先级最高的元素。top()
:返回优先级最高的元素的引用。empty()
:判断优先队列是否为空。size()
:返回优先队列中元素的数量。
6.拷贝构造函数为什么不能按值传递
1. 会引发无限递归调用
拷贝构造函数的用途是用一个已存在的对象来初始化另一个新对象。若拷贝构造函数按值传递参数,在调用拷贝构造函数时,会把实参复制给形参,而这个复制操作本身就需要调用拷贝构造函数。这就会造成无限递归调用,最终致使栈溢出错误。
cpp
#include <iostream>
class MyClass {
public:
// 错误示例:按值传递参数的拷贝构造函数
MyClass(MyClass obj) {
std::cout << "Copy constructor called" << std::endl;
}
// 构造函数
MyClass() {
std::cout << "Default constructor called" << std::endl;
}
};
int main() {
MyClass obj1;
// 尝试调用拷贝构造函数
MyClass obj2(obj1);
return 0;
}
在上述代码中,MyClass(MyClass obj)
是按值传递参数的拷贝构造函数。当执行 MyClass obj2(obj1);
时,为了把 obj1
传递给拷贝构造函数的形参 obj
,需要调用拷贝构造函数来复制 obj1
,而这个复制操作又会再次调用拷贝构造函数,如此循环,导致无限递归。
2. 按引用传递的解决方案
为了避免无限递归调用,拷贝构造函数通常按引用传递参数,一般是常量引用 const MyClass&
。这样在调用拷贝构造函数时,不会进行对象的复制,而是直接引用实参对象,从而避免了无限递归的问题。
cpp
#include <iostream>
class MyClass {
public:
// 正确示例:按常量引用传递参数的拷贝构造函数
MyClass(const MyClass& obj) {
std::cout << "Copy constructor called" << std::endl;
}
// 构造函数
MyClass() {
std::cout << "Default constructor called" << std::endl;
}
};
int main() {
MyClass obj1;
// 调用拷贝构造函数
MyClass obj2(obj1);
return 0;
}
在这个修正后的代码中,MyClass(const MyClass& obj)
是按常量引用传递参数的拷贝构造函数。当执行 MyClass obj2(obj1);
时,obj
直接引用 obj1
,不会触发新的拷贝构造函数调用,从而避免了无限递归。
3. 使用常量引用的好处
- 避免修改原对象 :使用
const
修饰引用参数,能保证在拷贝构造函数内部不会修改原对象,符合拷贝构造函数的语义。 - 支持临时对象:常量引用可以绑定到临时对象,这使得拷贝构造函数可以处理临时对象作为参数的情况。