点击上方“五分钟学算法”,选择“星标”公众号
重磅干货,第一时间送达
这是一篇, 关于 C 语言与 C++面试知识点总结的文章, 要是你们感觉到, 文章对自己, 具备一定的帮助作用, 那么在文末右下角, 点一下再看, 并且, 转发给更多的人。
const作用
修饰变量,说明该变量不可以被改变;
有一种对指针进行修饰的现象, 有指向这样变量的指针, 此变量是常量, 被称作指向常量的指针(pointer to const), 还有另一种指针, 其自身是常量, 被叫做常量指针, 也就是const pointer。
修改变换引用相关内容, 针对专门指向常量的那种引用, 也就是所谓的reference to const, 把该项用于形参的这种类型情况时, 一方面防止了拷贝行为的发生, 另一方面还杜绝了函数对于赋予值做出修改的情况出现。
修饰成员函数,说明该成员函数内不能修改成员变量。
const 的指针与引用
指针
引用
(为方便记忆能想成)那(被const修饰着且是在const之后的)值没办法改变, 就像下文使用例子里的p2又以及p3。
使用
// 类
class A
{
private:
const int a; // 常对象成员,只能在初始化列表赋值
public:
// 构造函数
A() : a(0) { };
A(int x) : a(x) { }; // 初始化列表
// const可用于对重载函数的区分
int getValue(); // 普通成员函数
int getValue() const; // 常成员函数,不得修改类中的任何数据成员的值
};
void function()
{
// 对象
A b; // 普通对象,可以调用全部成员函数、更新常成员变量
const A a; // 常对象,只能调用常成员函数
const A *p = &a; // 指针变量,指向常对象
const A &q = a; // 指向常对象的引用
// 指针
char greeting[] = "Hello";
char* p1 = greeting; // 指针变量,指向字符数组变量
const char* p2 = greeting; // 指针变量,指向字符数组常量(const 后面是 char,说明指向的字符(char)不可改变)
char* const p3 = greeting; // 自身是常量的指针,指向字符数组变量(const 后面是 p3,说明 p3 指针自身不可改变)
const char* const p4 = greeting; // 自身是常量的指针,指向字符数组常量
}
// 函数
void function1(const int Var); // 传递过来的参数在函数内不可变
void function2(const char* Var); // 参数指针所指内容为常量
void function3(char* const Var); // 参数指针为常量
void function4(const int& Var); // 引用参数在函数内为常量
// 函数返回值
const int function5(); // 返回一个常数
const int* function6(); // 返回一个指向常量的指针变量,使用:const int *p = function6();
int* const function7(); // 返回一个指向变量的常指针,使用:int* const p = function7();
static作用
被修饰的普通变量, 其存储区域以及生命周期会被改变, 致使变量被存储于静态区, 在main函数运行之前就已分配好空间, 要是存在初始值便使用该初始值对其进行初始化, 倘若没有初始值则由系统运用默认值来初始化它。
用于修饰普通函数, 以此来表明函数所具有的作用范围, 即仅仅是在定义该函数的那个文件之内才能够去使用。当处于多人共同进行开发项目的情况时, 为了达成防止与他人命名空间里的函数出现重名这种目的, 就能够把函数定位成 static。
使所有对象仅保留一个该变量的方式来修饰成员变量, 并且在不生成对象的情况下能够访问该成员变量, 以此来修饰成员变量。
一种对成员函数进行修饰的情况, 这种修饰能达成这样的效果, 即让函数在不生成对象的状况下也能够被访问, 然而, 对于处于static函数内部的情况而言, 是不可以对非静态成员进行访问的。
this 指针
这个this指针, 是一个特殊的指针, 它隐含于每一个非静态成员函数之中, 它指向那个调用该成员函数的对象。
对一个对象施行成员函数调用之时, 编译程序会先把对象的地址赋予 this 指针, 随后展开成员函数的调用操作, 于每次成员函数对数据成员进行存取之际, 皆会以隐式的方式运用 this 指针。
每当有一个成员函数被发起调用的时候, 就会自动朝着它传递一个隐含着的参数, 这个会被传递的隐含参数是一个指向这个成员函数所在对象的指针, 该指针指向着特定对象。
“this”指针被隐含声明成: “ClassName const this”, 这就意味着没法给“this”指针赋值;在“ClassName”类的“const”成员函数里, “this”指针的类型是: “const ClassName const”, 这表明对于“this”指针所指向的这样的对象是不可修改的(也就是不能针对这种对象的数据成员开展赋值操作)。
这, 并非是一个常规的变量, 而是属于右值一类, 因而不能够获取到这的地址(不可以使用&这)。
在以下场景中,经常需要显式引用 this 指针:
inline 内联函数特征使用
inline 使用
// 声明1(加 inline,建议使用)
inline int functionName(int first, int second,...);
// 声明2(不加 inline)
int functionName(int first, int second,...);
// 定义
inline int functionName(int first, int second,...) {/****/};
// 类内定义,隐式内联
class A {
int doA() { return 0; } // 隐式内联
}
// 类外定义,需要显式内联
class A {
int doA();
}
inline int A::doA() { return 0; } // 需要显式内联
编译器对 inline 函数处理步骤
把 inline 函数的函数体, 复制到 inline 函数被调用的那个位置处。
为所用 inline 函数中的局部变量分配内存空间;
把inline函数对应的输入参数, 以及返回值, 映射至调用方法的局部变量空间里。
倘若存在多返回点的内联函数, 针对此情形, 要把它转化为处于内联函数代码块末尾的依据条件跳转(运用GOTO语句)。
优缺点
优点
虚函数(virtual)可以是内联函数(inline)吗?
能够被"进行内联操作这种行为的具备这一特性的" 成员函数, 真的会永远如同被"进行内联操作这种行为的" 那样实际上被"进行内联操作这种行为的" 吗?
虚函数内联使用
#include
using namespace std;
class Base
{
public:
inline virtual void who()
{
cout << "I am Basen";
}
virtual ~Base() {}
};
class Derived : public Base
{
public:
inline void who() // 不写inline时隐式内联
{
cout << "I am Derivedn";
}
};
int main()
{
// 此处的虚函数 who(),是通过类(Base)的具体对象(b)来调用的,编译期间就能确定了,所以它可以是内联的,但最终是否内联取决于编译器。
Base b;
b.who();
// 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。
Base *ptr = new Derived();
ptr->who();
// 因为Base有虚析构函数(virtual ~Base() {}),所以 delete 时,会先调用派生类(Derived)析构函数,再调用基类(Base)析构函数,防止内存泄漏。
delete ptr;
ptr = nullptr;
system("pause");
return 0;
}
volatile
volatile int i = 10;
assert()
宣称, 属于宏, 并非函数。assert宏的原型界定于(C)、(C++)里, 其功效是要是它的条件返回差错, 那就终止程序运行。能够经由定义NDEBUG来关掉assert, 可是需在源代码的开头, 位于include之前。
assert() 使用
#define NDEBUG // 加上这行,则 assert 不可用
#include
assert( p != NULL ); // assert 不可用
sizeof()#pragma pack(n)
设定结构体、联合以及类成员变量以 n 字节方式对齐
#pragma pack(n) 使用
#pragma pack(push) // 保存对齐状态
#pragma pack(4) // 设定为 4 字节对齐
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop) // 恢复对齐状态
位域
Bit mode: 2; // mode 占 2 位
把类的(并非静态的)数据成员当成位域(bit-field)来进行定义, 在位域当中有着一定数量的二进制位, 在一个程序要向别的程序或者硬件设备去传递二进制数据之际,一般情况下就会用到位域。
extern "C"
extern"C" 具备这样一种作用, 这种作用是使得 C++ 编译器把 extern"C" 所声明的代码, 当作 C 语言代码来进行处理, 如此一来能够避免出现一种情况, 这种情况是 C++ 由于符号修饰而致使代码没办法和 C 语言库当中的符号去进行链接的问题。
extern "C" 使用
#ifdef __cplusplus
extern "C" {
#endif
void *memset(void *, int, size_t);
#ifdef __cplusplus
}
#endif
struct 和 typedef structC 中
// c
typedef struct Student {
int age;
} S;
等价于
// c
struct Student {
int age;
};
采用typedef对struct Student进行定义从而得到S, 在这个时候, S等同于struct Student, 然而, 这两个标识符的名称空间并非是相同的。
此外, 能够去定义, 与struct Student不存在冲突情况的, void Student() {}这样的内容。
C++ 中
因为编译器对符号进行定位的规则, 也就是搜索规则发生了改变, 所以造成了与C语言不一样的情况。
也就是呈现出能够运用 Student 的情况, 同时也能够运用 struct Student, 情况如下:
// cpp
struct Student {
int age;
};
void f( Student me ); // 正确,"struct" 关键字可省略
2.要是定义了跟 Student 同名的函数以后, 那么 Student 仅仅代表函数, 并不代表结构体, 以下是这样的情况:
typedef struct Student {
int age;
} S;
void Student() {} // 正确,定义后 "Student" 只代表此函数
//void S() {} // 错误,符号 "S" 已经被定义为一个 "struct Student" 的别名
int main() {
Student();
struct Student me; // 或者 "S me";
return 0;
}
C++ 中 struct 和 class
总而言之, struct 更适宜被看成是一个对数据结构进行实现的实体, class 更适宜被看成是一个对对象进行实现的实体。
区别:
最本质的一个区别就是默认的访问控制
union 联合
联合这种特殊的类具备节省空间的特性, 它拥有多个数据成员, 然而在任何一个时刻, 仅有一个数据成员能够存有值, 一旦某个成员被赋予了值, 其他成员便会转变为未定义的状态, 联合呈现出如下这些特点:
union 使用
#include
union UnionTest {
UnionTest() : i(10) {};
int i;
double d;
};
static union {
int i;
double d;
};
int main() {
UnionTest u;
union {
int i;
double d;
};
std::cout << u.i << std::endl; // 输出 UnionTest 联合的 10
::i = 20;
std::cout << ::i << std::endl; // 输出全局静态匿名联合的 20
i = 30;
std::cout << i << std::endl; // 输出局部匿名联合的 30
return 0;
}
C语言实现C++类
C 实现 C++ 的面向对象特性(封装、继承、多态)
explicit(显式)关键字explicit 使用
struct A
{
A(int) { }
operator bool() const { return true; }
};
struct B
{
explicit B(int) {}
explicit operator bool() const { return true; }
};
void doA(A a) {}
void doB(B b) {}
int main()
{
A a1(1); // OK:直接初始化
A a2 = 1; // OK:复制初始化
A a3{ 1 }; // OK:直接列表初始化
A a4 = { 1 }; // OK:复制列表初始化
A a5 = (A)1; // OK:允许 static_cast 的显式转换
doA(1); // OK:允许从 int 到 A 的隐式转换
if (a1); // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a6(a1); // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a7 = a1; // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a8 = static_cast(a1); // OK :static_cast 进行直接初始化
B b1(1); // OK:直接初始化
B b2 = 1; // 错误:被 explicit 修饰构造函数的对象不可以复制初始化
B b3{ 1 }; // OK:直接列表初始化
B b4 = { 1 }; // 错误:被 explicit 修饰构造函数的对象不可以复制列表初始化
B b5 = (B)1; // OK:允许 static_cast 的显式转换
doB(1); // 错误:被 explicit 修饰构造函数的对象不可以从 int 到 B 的隐式转换
if (b1); // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
bool b6(b1); // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
bool b7 = b1; // 错误:被 explicit 修饰转换函数 B::operator bool() 的对象不可以隐式转换
bool b8 = static_cast(b1); // OK:static_cast 进行直接初始化
return 0;
}
friend 友元类和友元函数usingusing 声明
使用声明语句, 一条, 该语句一次仅能引入命名空间的单一成员。它能让我们清晰知晓程序里所引用的究竟是何方名字。比如:
using namespace_name::name;
构造函数的 using 声明
于C++11里, 派生类具备可重用其直接基类所定义的构造函数的能力。
class Derived : Base {
public:
using Base::Base;
/* ... */
};
如上述 using 声明, 针对于基类的每一个构造函数而言, 编译器都会生成一个跟它相对应的派生类构造函数, 此派生类构造函数的形参列表与基类构造函数的形参列表是完全相同的。会生成像下面这种类型的构造函数: Derived(parms) : Base(args)。
using 指示
使用指示, 能让某个特定命名空间里的所有名字都变得可见, 如此一来, 我们就不用再给它们添加任何前缀限定符了。就像这样:
using namespace_name name;
尽量少使用 using 指示 污染命名空间
通常来讲, 运用using命令相较于运用using编译命令会更具安全性, 这是因为它仅仅导入了所指定的名称。要是该名称和局部名称产生冲突, 编译器就会发出指示。using编译命令会导入所有的名称, 其中涵盖了有可能并不需要的名称。要是与局部名称起了冲突, 那么局部名称将会覆盖名称空间版本, 并且编译器不会发出警告。此外, 名称空间的开放性意味着名称空间的名称有可能分散于多个位置, 这致使难以确切知晓添加了哪些名称。
using 使用
尽量少使用 using 指示
using namespace std;
应该多使用 using 声明
int x;
std::cin >> x ;
std::cout << x << std::endl;
或者
using std::cin;
using std::cout;
using std::endl;
int x;
cin >> x;
cout << x << endl;
:: 范围解析运算符分类
用于类型名称(类、类成员、成员函数、变量等)前的全局作用域符(::name), 其表示作用域为全局命名空间。
类作用域符, 具体为 class::name, 其作用在于表明, 指定类型的作用域范围, 乃是具体的、某个类的。
那个可以用来表示特定类型的作用域范围属于具体某一个命名空间的, 是命名空间作用域符(namespace::name)。
:: 使用
int count = 11; // 全局(::)的 count
class A {
public:
static int count; // 类 A 的 count(A::count)
};
int A::count = 21;
void fun()
{
int count = 31; // 初始化局部的 count 为 31
count = 32; // 设置局部的 count 的值为 32
}
int main() {
::count = 12; // 测试 1:设置全局的 count 的值为 12
A::count = 22; // 测试 2:设置类 A 的 count 为 22
fun(); // 测试 3
return 0;
}
enum 枚举类型
定作用域的枚举类型
通过枚举类定义了一种名为open_modes的类型, 它包含了input、output、append这几个值。
不限定作用域的枚举类型
enum color { red, yellow, green };
enum { floatPrec = 6, doublePrec = 10 };
decltype

decltype关键字, 被用来检查实体的声明类型, 或者用于检查表达式的类型以及值分类, 其语法为:
decltype ( expression )
decltype 使用
// 尾置返回允许我们在参数列表之后声明返回类型
template
auto fcn(It beg, It end) -> decltype(*beg)
{
// 处理序列
return *beg; // 返回序列中一个元素的引用
}
// 为了使用模板参数成员,必须用 typename
template
auto fcn2(It beg, It end) -> typename remove_reference::type
{
// 处理序列
return *beg; // 返回序列中一个元素的拷贝
}
引用左值引用
常规引用,一般表示对象的身份。
右值引用
右值引用,是一种引用, 它必须绑定到右值, 右值是指一个临时对象, 或者是将要被销毁的对象, 这种引用一般用来表示对象的值。
对右值参照来说的话, 能够达成转移语义以及精确传递这两项情况, 而其主要所要达成的目标, 存在着两个不同的方面。
引用折叠宏
宏定义能够达成跟函数相类似的功能, 然而它终究并非函数, 并且宏定义里括弧之中的那份“参数”并非实实在在的参数, 于宏展开之际针对“参数”开展的是一对一的替换操作。
成员初始化列表
好处
更高效:少了一次调用默认构造函数的过程。
有些场合必须要用初始化列表:
initializer_list 列表初始化
初始化一个对象, 使用花括号初始化器列表, 而对应构造函数接受一个std::initializer_list参数。
initializer_list 使用
#include
#include
#include
template
struct S {
std::vector v;
S(std::initializer_list l) : v(l) {
std::cout << "constructed with a " << l.size() << "-element listn";
}
void append(std::initializer_list l) {
v.insert(v.end(), l.begin(), l.end());
}
std::pair c_arr() const {
return {&v[0], v.size()}; // 在 return 语句中复制列表初始化
// 这不使用 std::initializer_list
}
};
template
void templated_fn(T) {}
int main()
{
S s = {1, 2, 3, 4, 5}; // 复制初始化
s.append({6, 7, 8}); // 函数调用中的列表初始化
std::cout << "The vector size is now " << s.c_arr().second << " ints:n";
for (auto n : s.v)
std::cout << n << ' ';
std::cout << 'n';
std::cout << "Range-for over brace-init-list: n";
for (int x : {-1, -2, -3}) // auto 的规则令此带范围 for 工作
std::cout << x << ' ';
std::cout << 'n';
auto al = {10, 11, 12}; // auto 的特殊规则
std::cout << "The list bound to auto has size() = " << al.size() << 'n';
// templated_fn({1, 2, 3}); // 编译错误!“ {1, 2, 3} ”不是表达式,
// 它无类型,故 T 无法推导
templated_fn>({1, 2, 3}); // OK
templated_fn>({1, 2, 3}); // 也 OK
}
面向对象
种具有对象概念的程序编程典范, 程序开发里边的一种抽象方针, 是面向对象程序设计, 也就是Object-oriented programming, 简称OOP。
面向对象特征
面向对象三大特征 —— 封装、继承、多态
封装
把事物按客观情况进行封装, 使其成为抽象的类, 而且这个类能够将自身所拥有的数据以及方法, 仅仅给予那些可信任的类或者对象来操作, 对于不可信任的则实施信息隐藏处理。其中涉及的关键字包括: public、protected、private。要是不进行书写, 那么默认状态为 private。
继承多态
多种的样式(形态), 也就是所谓的多态。简而言之, 则是能够把消息由多种方式呈现显示出来的那种能力, 这便是我们可以对多态施行设定定义的内容。
多态是以封装和继承为基础的。
C++ 多态分类及实现:
静态多态(编译期/早绑定)
函数重载
class A
{
public:
void do(int a);
void do(int a, int b);
};
动态多态(运行期期/晚绑定)
注意:
动态多态使用
class Shape // 形状类
{
public:
virtual double calcArea()
{
...
}
virtual ~Shape();
};
class Circle : public Shape // 圆形类
{
public:
virtual double calcArea();
...
};
class Rect : public Shape // 矩形类
{
public:
virtual double calcArea();
...
};
int main()
{
Shape * shape1 = new Circle(4.0);
Shape * shape2 = new Rect(5.0, 6.0);
shape1->calcArea(); // 调用圆形类里面的方法
shape2->calcArea(); // 调用矩形类里面的方法
delete shape1;
shape1 = nullptr;
delete shape2;
shape2 = nullptr;
return 0;
}
虚析构函数
虚析构函数存在的目的, 是去处理这样一种情况, 即基类的指针指向了派生类对象, 随后通过基类的指针去删除派生类对象。
虚析构函数使用
class Shape
{
public:
Shape(); // 构造函数不能是虚函数
virtual double calcArea();
virtual ~Shape(); // 虚析构函数
};
class Circle : public Shape // 圆形类
{
public:
virtual double calcArea();
...
};
int main()
{
Shape * shape1 = new Circle(4.0);
shape1->calcArea();
delete shape1; // 因为Shape有虚析构函数,所以delete释放内存时,先调用子类析构函数,再调用基类析构函数,防止内存泄漏。
shape1 = NULL;
return 0;
}
纯虚函数
纯虚函数身为一种特殊的虚函数, 于基类里无法对虚函数给出具有意义的实现之情形, 而将其声明成纯虚函数, 它的实现交付给该基类的派生类去履行。
virtual int A() = 0;
虚函数、纯虚函数虚函数指针、虚函数表虚继承
钻石形状的继承问题, 就是在多继承的状况下会出现的, 它会造成存储空间的浪费, 还会存在二义性, 而虚继承就是专门用来处理该问题的。
底层实现原理, 和编译器是相关的, 通常借助虚基类指针以及虚基类表来达成, 每一个虚继承的子类, 都具备一个虚基类指针, 这占用一个指针的存储空间, 也就是4字节, 同时还有虚基类表, 它不占用类对象的存储空间, 需要着重说明的是, 虚基类在子类当中依旧会存在拷贝情况, 只是仅仅最多存在一份罢了, 并非不在子类里面了, 当虚继承的子类被当成父类继承时, 虚基类指针也会被继承。
事实上, vbptr所指的乃是虚基类表指针, 也就是virtual base table pointer, 此指针指向了一个虚基类表, 即virtual table, 虚表之中记录着虚基类与本类的偏移地址, 借助偏移地址便找到了虚基类成员, 并且虚继承并非如同普通多继承那般维持着公共基类即虚基类的两份相同拷贝, 从而节省了存储空间。
虚继承、虚函数
同为之处在于, 都运用了虚指针, 此虚指针皆占用类的存储空间, 并且都采用了虚表, 该虚表同样不占用类的存储空间。
不同之处:
虚继承
虚函数
模板类、成员模板、虚函数抽象类、接口类、聚合类
抽象类:含有纯虚函数的类
接口类:仅含有纯虚函数的抽象类
聚合类: 用户能够直接对其成员进行访问, 并且具备特殊的初始化语法形式, 呈现出如下特点:
进行内存分配以及展开管理的malloc, 还有calloc这项, 再者是realloc, 另外还有alloca。
malloc这项操作, 是用于去申请那指定字节数的内存空间 , 而申请得到的内存之中, 其初始的值是处于不确定的状态。
alloc: 针对指定长度的对象, 去分配可容纳其指定个数的内存, 所申请到的内存的每一个位都被初始化成0。
重新分配内存: 对前已分配的内存长度予以更改(无论是增加还是减少)情况。当处于增加内存长度这种状况的时候, 有可能需要把之前所分配区域当中的内容转移至另外一个具备足够大小的区域, 然而新增加区域范围内的起始值是不确定的。
“alloca”: 于栈之上的地方去申请内存, 当程序进行出栈这个操作的时候, 会自动地将内存予以释放, 然而需要加以留意的是, “alloca”并不具备可移植的性能, 并且在那些不存在传统堆栈的机器上面是很难达成实现的, “alloca”在要求必须广泛地具有可移植性的程序里面是不适宜去使用的, 在C99当中存在着支持变长数组(即为VLA)的情况, 能够运用该变长数组来把“alloca”给替换掉。
malloc、free
用于分配、释放内存
malloc、free 使用
申请内存,确认是否申请成功
char *str = (char*) malloc(100);
assert(str != nullptr);
释放内存后指针置空
free(p);
p = nullptr;
new、delete
新的, 新的意思是: 先在底层借助 malloc 来分配内存, 完成这一步之后, 再去调用构造函数, 也就是创建对象, 从而达成两件事。
delete操作会完成两件事情, 首先会去调用析构函数, 此析构函数是用于清理资源的, 接着在底层使用free来释放相应的空间。
new在申请内存之际的时候, 会自动去计算申请内存之际所需的、那些字节数, 然而呢, malloc这个却是需要我们自己对申请内存空间所需要达到的字节数动手去输入的!
new、delete 使用
申请内存,确认是否申请成功
int main()
{
T* t = new T(); // 先内存分配 ,再构造函数
delete t; // 先析构函数,再内存释放
return 0;
}
定位 new
定位new, 也就是placement new, 它能让我们给new传送额外的地址参数, 进而在预先设定好的内存区域当中创建对象。
new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size] { braced initializer list }
delete this 合法吗?
合法,但:
定义只在堆(栈)生成对象类只能在堆上
方法:将析构函数设置为私有
原因是, C++这话呀, 它乃是静态绑定语言, 那编译器呢在管理着栈上对象的生命周期。当编译器去操心为类对象这儿分配栈空间的时候, 它得先仔仔细细地检查一下类的析构函数那种访问性。一旦发现析构函数不具备可访问性这特性状况, 那就不可以在栈上创建对象了。
能在栈上
方法:将 new 和 delete 重载为私有
理由是: 于堆里产生对象, 借助 new 关键字来操作, 此过程划分成两个阶段, 其一阶段, 运用 new 在堆中寻觅可用内存, 分配给对象;其二阶段, 调用构造函数来生成对象。要是把 new 操作设定为私有, 那么第一阶段就没办法达成, 就不能够在堆上生成对象了。
智能指针C++ 标准库(STL)中
头文件:#include
C++ 98
std::auto_ptr ps (new std::string(str));
C++ 11
对于该论述,欢迎读者查阅之前发过的文章,
3.7 分割线
能够于一行之中借由三个以上的减号去构建一条分隔线, 并且要于分隔线的上方空出一行, 情形如下:
shared_ptr
unique_ptr
weak_ptr
auto_ptr(被 C++11 弃用)
shared_ptr
许许多多的智能指针能够共同分享同一个对象, 那个对象的处于序列最末尾位置的拥有者负有将对象予以销毁的责任, 并且还要对与该对象存在关联的所有资源进行清理。
weak_ptr
将某个对象进行共享但并非握有该对象所有权的是 weak_ptr, 一旦那最后个持有该对象的智能指针丧失了所有权之际, 任意一个 weak_ptr 都会自动变为空(成为 empty 状态)。鉴于此情形, 在排除 default 和 copy 构造函数之外, weak_ptr 唯独提供那种 “负责接纳一个 shared_ptr” 的构造函数。
unique_ptr
unique_ptr 这种类型是从 C++11 才开始有的, 它属于智能指针, 在存在异常的情况下能够助力避免资源泄漏, 它采用独占式拥有的方式, 这意味着能够保证一个对象以及与其对应的资源在同一时刻仅仅被一个 pointer 所拥有, 一旦拥有者被销毁或者变为 empty, 又或者开始拥有另一个对象, 那么先前被拥有的那个对象就会被销毁, 它的任何相应资源也会被释放。
auto_ptr
由于欠缺像 “针对构造和赋值” 这般的语言特性, 也就是 std::move 语义, 还有别的一些瑕疵, 所以它被 c++11 给弃用了。
auto_ptr 与 unique_ptr 比较
bad_cast 使用
try {
Circle& ref_circle = dynamic_cast(ref_shape);
}
catch (bad_cast b) {
cout << "Caught: " << b.what();
}
typeid、type_info 使用
#include
using namespace std;
class Flyable // 能飞的
{
public:
virtual void takeoff() = 0; // 起飞
virtual void land() = 0; // 降落
};
class Bird : public Flyable // 鸟
{
public:
void foraging() {...} // 觅食
virtual void takeoff() {...}
virtual void land() {...}
virtual ~Bird(){}
};
class Plane : public Flyable // 飞机
{
public:
void carry() {...} // 运输
virtual void takeoff() {...}
virtual void land() {...}
};
class type_info
{
public:
const char* name() const;
bool operator == (const type_info & rhs) const;
bool operator != (const type_info & rhs) const;
int before(const type_info & rhs) const;
virtual ~type_info();
private:
...
};
void doSomething(Flyable *obj) // 做些事情
{
obj->takeoff();
cout << typeid(*obj).name() << endl; // 输出传入对象类型("class Bird" or "class Plane")
if(typeid(*obj) == typeid(Bird)) // 判断对象类型
{
Bird *bird = dynamic_cast(obj); // 对象转化
bird->foraging();
}
obj->land();
}
int main(){
Bird *b = new Bird();
doSomething(b);
delete b;
b = nullptr;
return 0;
}
参考资料
https://github.com/huihut/interview#effective

CopyrightC 2009-2025 All Rights Reserved 版权所有 芜湖人才网 本站内容仅供参考,不承担因使用信息、外部链接或服务中断导致的任何直接或间接责任,风险自担。如有侵权,请联系删除,联系邮箱:ysznh@foxmail.com 鄂ICP备2025097818号-15
地址: EMAIL:qlwl@foxmail.com
Powered by PHPYun.