app设计网站模板,org后缀的网站,同一人可以做几个网站的负责人,怎样进行网站推广点击蓝字关注我们相对而言#xff0c;C语言和C相关的面试题比较少见#xff0c;没有Java方向写的人那么多#xff0c;这是一篇 C 语言与 C面试知识点总结的文章#xff0c;个人感觉非常难得#xff0c;希望能对大家有所帮助。const作用修饰变量#xff0c;说明该变量不可… 点击蓝字关注我们相对而言C语言和C相关的面试题比较少见没有Java方向写的人那么多这是一篇 C 语言与 C面试知识点总结的文章个人感觉非常难得希望能对大家有所帮助。const作用修饰变量说明该变量不可以被改变修饰指针分为指向常量的指针pointer to const和自身是常量的指针常量指针const pointer修饰引用指向常量的引用reference to const用于形参类型即避免了拷贝又避免了函数对值的修改修饰成员函数说明该成员函数内不能修改成员变量。const 的指针与引用指针指向常量的指针pointer to const自身是常量的指针常量指针const pointer引用指向常量的引用reference to const没有 const reference因为引用本身就是 const pointer为了方便记忆可以想成被 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 并不是一个常规变量而是个右值所以不能取得 this 的地址不能 this。在以下场景中经常需要显式引用 this 指针为实现对象的链式引用为避免对同一对象进行赋值操作在实现一些数据结构时如 list。inline 内联函数特征相当于把内联函数里面的内容写在调用内联函数处相当于不用执行进入函数的步骤直接执行函数体相当于宏却比宏多了类型检查真正具有函数特性编译器一般不内联包含循环、递归、switch 等复杂操作的内联函数在类声明中定义的函数除了虚函数的其他函数都会自动隐式地当成内联函数。使用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 函数的的输入参数和返回值映射到调用方法的局部变量空间中如果 inline 函数有多个返回点将其转变为 inline 函数代码块末尾的分支使用 GOTO。优缺点优点内联函数同宏函数一样将在被调用处进行代码展开省去了参数压栈、栈帧开辟与回收结果返回等从而提高程序运行速度。内联函数相比宏函数来说在代码展开时会做安全检查或自动类型转换同普通函数而宏定义则不会。在类中声明同时定义的成员函数自动转化为内联函数因此内联函数可以访问类的成员变量宏定义则不能。内联函数在运行时可调试而宏定义不可以。虚函数virtual可以是内联函数inline吗Are inline virtual member functions ever actually inlined?虚函数可以是内联函数内联是可以修饰虚函数的但是当虚函数表现多态性的时候不能内联。内联是在编译器建议编译器内联而虚函数的多态性在运行期编译器无法知道运行期调用哪个代码因此虚函数表现为多态性时运行期不可以内联。inline virtual 唯一可以内联的时候是编译器知道所调用的对象是哪个类如 Base::who()这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。虚函数内联使用#include iostream
using namespace std;
class Base
{
public:
inline virtual void who()
{
cout I am Base\n;
}
virtual ~Base() {}
};
class Derived : public Base
{
public:
inline void who() // 不写inline时隐式内联
{
cout I am Derived\n;
}
};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;
}volatilevolatile int i 10;volatile 关键字是一种类型修饰符用它声明的类型变量表示可以被某些编译器未知的因素操作系统、硬件、其它线程等更改。所以使用 volatile 告诉编译器不应对这样的对象进行优化。volatile 关键字声明的变量每次访问时都必须从内存中取出值没有被 volatile 修饰的变量可能由于编译器的优化从 CPU 寄存器中取值const 可以是 volatile 如只读的状态寄存器指针可以是 volatileassert()断言是宏而非函数。assert 宏的原型定义在 assert.hC、C中其作用是如果它的条件返回错误则终止程序执行。可以通过定义 NDEBUG 来关闭 assert但是需要在源代码的开头include assert.h 之前。assert() 使用#define NDEBUG // 加上这行则 assert 不可用
#include assert.hassert( p ! NULL ); // assert 不可用sizeof()sizeof 对数组得到整个数组所占空间大小。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 限定的函数或变量是 extern 类型的被 extern C 修饰的变量和函数是按照 C 语言方式编译和链接的extern C 的作用是让 C 编译器将 extern C 声明的代码当作 C 语言代码处理可以避免 C 因符号修饰导致代码不能和C语言库中的符号进行链接的问题。extern C 使用#ifdef __cplusplus
extern C {
#endifvoid *memset(void *, int, size_t);#ifdef __cplusplus
}
#endifstruct 和 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语言。1.如果在类标识符空间定义了 struct Student {...};使用 Student me; 时编译器将搜索全局标识符表Student 未找到则在类标识符内搜索。即表现为可以使用 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 更适合看成是一个对象的实现体。区别:最本质的一个区别就是默认的访问控制默认的继承访问权限。struct 是 public 的class 是 private 的。struct 作为数据结构的实现体它默认的数据访问控制是 public 的而 class 作为对象的实现体它默认的成员变量访问控制是 private 的。union 联合联合union是一种节省空间的特殊的类一个 union 可以有多个数据成员但是在任意时刻只有一个数据成员可以有值。当某个成员被赋值后其他成员变为未定义状态。联合有如下特点默认访问控制符为 public可以含有构造函数、析构函数不能含有引用类型的成员不能继承自其他类不能作为基类不能含有虚函数匿名 union 在定义所在作用域可直接访问 union 成员匿名 union 不能包含 protected 成员或 private 成员全局匿名联合必须是静态static的union 使用#includeiostreamunion 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; // 输出全局静态匿名联合的 20i 30;std::cout i std::endl; // 输出局部匿名联合的 30return 0;
}C语言实现C类C 实现 C 的面向对象特性封装、继承、多态封装使用函数指针把属性与方法封装到结构体中继承结构体嵌套多态父类与子类方法的函数指针不同explicit显式关键字explicit 修饰构造函数时可以防止隐式转换和复制初始化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 a6a1;// OK使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a7 a1;// OK使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a8 static_castbool(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_castbool(b1); // OKstatic_cast 进行直接初始化return 0;
}friend 友元类和友元函数能访问私有成员破坏封装性友元关系不可传递友元关系的单向性友元声明的形式及数量不受限制usingusing 声明一条 using 声明 语句一次只引入命名空间的一个成员。它使得我们可以清楚知道程序中所引用的到底是哪个名字。如using namespace_name::name;构造函数的 using 声明在 C11 中派生类能够重用其直接基类定义的构造函数。class Derived : Base {
public:using Base::Base;/* ... */
};如上 using 声明对于基类的每个构造函数编译器都生成一个与之对应形参列表完全相同的派生类构造函数。生成如下类型构造函数Derived(parms) : Base(args) { }using 指示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; // 全局::的 countclass A {
public:
static int count; // 类 A 的 countA::count
};
int A::count 21;void fun()
{
int count 31; // 初始化局部的 count 为 31
count 32; // 设置局部的 count 的值为 32
}int main() {
::count 12; // 测试 1设置全局的 count 的值为 12A::count 22; // 测试 2设置类 A 的 count 为 22fun(); // 测试 3return 0;
}enum 枚举类型定作用域的枚举类型enum class open_modes { input, output, append };不限定作用域的枚举类型enum color { red, yellow, green };enum { floatPrec 6, doublePrec 10 };decltypedecltype 关键字用于检查实体的声明类型或表达式的类型及值分类。语法decltype ( expression )decltype 使用// 尾置返回允许我们在参数列表之后声明返回类型
template typename It
auto fcn(It beg, It end) - decltype(*beg)
{// 处理序列return *beg; // 返回序列中一个元素的引用
}
// 为了使用模板参数成员必须用 typename
template typename It
auto fcn2(It beg, It end) - typename remove_referencedecltype(*beg)::type
{// 处理序列return *beg; // 返回序列中一个元素的拷贝
}引用左值引用常规引用一般表示对象的身份。右值引用右值引用就是必须绑定到右值一个临时对象、将要销毁的对象的引用一般表示对象的值。右值引用可实现转移语义Move Sementics和精确传递Perfect Forwarding它的主要目的有两个方面消除两个对象交互时不必要的对象拷贝节省运算存储资源提高效率。能够更简洁明确地定义泛型函数。引用折叠X 、X 、X 可折叠成 XX 可折叠成 X宏宏定义可以实现类似于函数的功能但是它终归不是函数而宏定义中括弧中的“参数”也不是真的参数在宏展开的时候对 “参数” 进行的是一对一的替换。成员初始化列表好处更高效少了一次调用默认构造函数的过程。有些场合必须要用初始化列表常量成员因为常量只能初始化不能赋值所以必须放在初始化列表里面引用类型引用必须在定义的时候初始化并且不能重新赋值所以也要写在初始化列表里面没有默认构造函数的类类型因为使用初始化列表可以不必调用默认构造函数来初始化initializer_list 列表初始化用花括号初始化器列表初始化一个对象其中对应构造函数接受一个 std::initializer_list 参数.initializer_list 使用#include iostream
#include vector
#include initializer_listtemplate class T
struct S {std::vectorT v;S(std::initializer_listT l) : v(l) {std::cout constructed with a l.size() -element list\n;}void append(std::initializer_listT l) {v.insert(v.end(), l.begin(), l.end());}std::pairconst T*, std::size_t c_arr() const {return {v[0], v.size()}; // 在 return 语句中复制列表初始化// 这不使用 std::initializer_list}
};template typename T
void templated_fn(T) {}int main()
{Sint 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_fnstd::initializer_listint({1, 2, 3}); // OKtemplated_fnstd::vectorint({1, 2, 3}); // 也 OK
}面向对象面向对象程序设计Object-oriented programmingOOP是种具有对象概念的程序编程典范同时也是一种程序开发的抽象方针。面向对象特征面向对象三大特征 —— 封装、继承、多态封装把客观事物封装成抽象的类并且类可以把自己的数据和方法只让可信的类或者对象操作对不可信的进行信息隐藏。关键字public, protected, private。不写默认为 private。public 成员可以被任意实体访问protected 成员只允许被子类及本类的成员函数访问private 成员只允许被本类的成员函数、友元类或友元函数访问继承基类父类—— 派生类子类多态多态即多种状态形态。简单来说我们可以将多态定义为消息以多种形式显示的能力。多态是以封装和继承为基础的。C 多态分类及实现重载多态Ad-hoc Polymorphism编译期函数重载、运算符重载子类型多态Subtype Polymorphism运行期虚函数参数多态性Parametric Polymorphism编译期类模板、函数模板强制多态Coercion Polymorphism编译期/运行期基本类型转换、自定义类型转换静态多态编译期/早绑定函数重载class A
{
public:void do(int a);void do(int a, int b);
};动态多态运行期期/晚绑定虚函数用 virtual 修饰成员函数使其成为虚函数注意普通函数非类成员函数不能是虚函数静态函数static不能是虚函数构造函数不能是虚函数因为在调用构造函数时虚表指针并没有在对象的内存空间中必须要构造函数调用完成后才会形成虚表指针内联函数不能是表现多态性时的虚函数解释见虚函数virtual可以是内联函数inline吗动态多态使用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;虚函数、纯虚函数类里如果声明了虚函数这个函数是实现的哪怕是空实现它的作用就是为了能让这个函数在它的子类里面可以被覆盖override这样的话编译器就可以使用后期绑定来达到多态了。纯虚函数只是一个接口是个函数的声明而已它要留到子类里去实现。虚函数在子类里面可以不重写但纯虚函数必须在子类实现才可以实例化子类。虚函数的类用于 “实作继承”继承接口的同时也继承了父类的实现。纯虚函数关注的是接口的统一性实现由子类完成。带纯虚函数的类叫抽象类这种类不能直接生成对象而只有被继承并重写其虚函数后才能使用。抽象类被继承后子类可以继续是抽象类也可以是普通类。虚基类是虚继承中的基类具体见下文虚继承。虚函数指针、虚函数表虚函数指针在含有虚函数类的对象中指向虚函数表在运行时确定。虚函数表在程序只读数据段.rodata section见目标文件存储结构存放虚函数指针如果派生类实现了基类的某个虚函数则在虚表中覆盖原本基类的那个虚函数指针在编译时根据类的声明创建。虚继承虚继承用于解决多继承条件下的菱形继承问题浪费存储空间、存在二义性。底层实现原理与编译器相关一般通过虚基类指针和虚基类表实现每个虚继承的子类都有一个虚基类指针占用一个指针的存储空间4字节和虚基类表不占用类对象的存储空间需要强调的是虚基类依旧会在子类里面存在拷贝只是仅仅最多存在一份而已并不是不在子类里面了当虚继承的子类被当做父类继承时虚基类指针也会被继承。实际上vbptr 指的是虚基类表指针virtual base table pointer该指针指向了一个虚基类表virtual table虚表中记录了虚基类与本类的偏移地址通过偏移地址这样就找到了虚基类成员而虚继承也不用像普通多继承那样维持着公共基类虚基类的两份同样的拷贝节省了存储空间。虚继承、虚函数相同之处都利用了虚指针均占用类的存储空间和虚表均不占用类的存储空间不同之处虚继承虚基类依旧存在继承类中只占用存储空间虚基类表存储的是虚基类相对直接继承类的偏移虚函数虚函数不占用存储空间虚函数表存储的是虚函数地址模板类、成员模板、虚函数模板类中可以使用虚函数一个类无论是普通类还是类模板的成员模板本身是模板的成员函数不能是虚函数抽象类、接口类、聚合类抽象类含有纯虚函数的类接口类仅含有纯虚函数的抽象类聚合类用户可以直接访问其成员并且具有特殊的初始化语法形式。满足如下特点所有成员都是 public没有定义任何构造函数没有类内初始化没有基类也没有 virtual 函数内存分配和管理malloc、calloc、realloc、allocamalloc申请指定字节数的内存。申请到的内存中的初始值不确定。calloc为指定长度的对象分配能容纳其指定个数的内存。申请到的内存的每一位bit都初始化为 0。realloc更改以前分配的内存长度增加或减少。当增加长度时可能需将以前分配区的内容移到另一个足够大的区域而新增区域内的初始值则不确定。alloca在栈上申请内存。程序在出栈的时候会自动释放内存。但是需要注意的是alloca 不具可移植性, 而且在没有传统堆栈的机器上很难实现。alloca 不宜使用在必须广泛移植的程序中。C99 中支持变长数组 (VLA)可以用来替代 alloca。malloc、free用于分配、释放内存malloc、free 使用申请内存确认是否申请成功char *str (char*) malloc(100);
assert(str ! nullptr);释放内存后指针置空free(p);
p nullptr;new、deletenew / new[]完成两件事先底层调用 malloc 分配了内存然后调用构造函数创建对象。delete/delete[]也完成两件事先调用析构函数清理资源然后底层调用 free 释放空间。new 在申请内存时会自动计算所需字节数而 malloc 则需我们自己输入申请内存空间的字节数。new、delete 使用申请内存确认是否申请成功int main()
{T* t new T(); // 先内存分配 再构造函数delete t; // 先析构函数再内存释放return 0;
}定位 new定位 newplacement new允许我们向 new 传递额外的地址参数从而在预先指定的内存区域创建对象。new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size] { braced initializer list }place_address 是个指针initializers 提供一个可能为空的以逗号分隔的初始值列表delete this 合法吗合法但必须保证 this 对象是通过 new不是 new[]、不是 placement new、不是栈上、不是全局、不是其他对象成员分配的必须保证调用 delete this 的成员函数是最后一个调用 this 的成员函数必须保证成员函数的 delete this 后面没有调用 this 了必须保证 delete this 后没有人使用了定义只在堆(栈)生成对象类只能在堆上方法将析构函数设置为私有原因C 是静态绑定语言编译器管理栈上对象的生命周期编译器在为类对象分配栈空间时会先检查类的析构函数的访问性。若析构函数不可访问则不能在栈上创建对象。能在栈上方法将 new 和 delete 重载为私有原因在堆上生成对象使用 new 关键词操作其过程分为两阶段第一阶段使用 new 在堆上寻找可用内存分配给对象第二阶段调用构造函数生成对象。将 new 操作设置为私有那么第一阶段就无法完成就不能够在堆上生成对象。智能指针C 标准库STL中头文件#include memoryC 98std::auto_ptrstd::string ps (new std::string(str))C 113.7 分割线可以在一行中用三个以上的减号来建立一个分隔线同时需要在分隔线的上面空一行。如下shared_ptrunique_ptrweak_ptrauto_ptr被 C11 弃用Class shared_ptr 实现共享式拥有shared ownership概念。多个智能指针指向相同对象该对象和其相关资源会在 “最后一个 reference 被销毁” 时被释放。为了在结构较复杂的情景中执行上述工作标准库提供 weak_ptr、bad_weak_ptr 和 enable_shared_from_this 等辅助类。Class unique_ptr 实现独占式拥有exclusive ownership或严格拥有strict ownership概念保证同一时间内只有一个智能指针可以指向该对象。你可以移交拥有权。它对于避免内存泄漏resource leak——如 new 后忘记 delete ——特别有用。shared_ptr多个智能指针可以共享同一个对象对象的最末一个拥有着有责任销毁对象并清理与该对象相关的所有资源。支持定制型删除器custom deleter可防范 Cross-DLL 问题对象在动态链接库DLL中被 new 创建却在另一个 DLL 内被 delete 销毁、自动解除互斥锁weak_ptrweak_ptr 允许你共享但不拥有某对象一旦最末一个拥有该对象的智能指针失去了所有权任何 weak_ptr 都会自动成空empty。因此在 default 和 copy 构造函数之外weak_ptr 只提供 “接受一个 shared_ptr” 的构造函数。可打破环状引用cycles of references两个其实已经没有被使用的对象彼此互指使之看似还在 “被使用” 的状态的问题unique_ptrunique_ptr 是 C11 才开始提供的类型是一种在异常时可以帮助避免资源泄漏的智能指针。采用独占式拥有意味着可以确保一个对象和其相应的资源同一时间只被一个 pointer 拥有。一旦拥有着被销毁或编程 empty或开始拥有另一个对象先前拥有的那个对象就会被销毁其任何相应资源亦会被释放。unique_ptr 用于取代 auto_ptrauto_ptr被 c11 弃用原因是缺乏语言特性如 “针对构造和赋值” 的 std::move 语义以及其他瑕疵。auto_ptr 与 unique_ptr 比较auto_ptr 可以赋值拷贝复制拷贝后所有权转移unqiue_ptr 无拷贝赋值语义但实现了move 语义auto_ptr 对象不能管理数组析构调用 deleteunique_ptr 可以管理数组析构调用 delete[] 强制类型转换运算符static_cast用于非多态类型的转换不执行运行时类型检查转换安全性不如 dynamic_cast通常用于转换数值数据类型如 float - int可以在整个类层次结构中移动指针子类转化为父类安全向上转换父类转化为子类不安全因为子类可能有不在父类的字段或方法dynamic_cast用于多态类型的转换执行行运行时类型检查只适用于指针或引用对不明确的指针的转换将失败返回 nullptr但不引发异常可以在整个类层次结构中移动指针包括向上转换、向下转换const_cast用于删除 const、volatile 和 __unaligned 特性如将 const int 类型转换为 int 类型 reinterpret_cast用于位的简单重新解释滥用 reinterpret_cast 运算符可能很容易带来风险。除非所需转换本身是低级别的否则应使用其他强制转换运算符之一。允许将任何指针转换为任何其他指针类型如 char* 到 int* 或 One_class* 到 Unrelated_class* 之类的转换但其本身并不安全也允许将任何整数类型转换为任何指针类型以及反向转换。reinterpret_cast 运算符不能丢掉 const、volatile 或 __unaligned 特性。reinterpret_cast 的一个实际用途是在哈希函数中即通过让两个不同的值几乎不以相同的索引结尾的方式将值映射到索引。bad_cast由于强制转换为引用类型失败dynamic_cast 运算符引发 bad_cast 异常。bad_cast 使用try {Circle ref_circle dynamic_castCircle(ref_shape);
}
catch (bad_cast b) {cout Caught: b.what();
}运行时类型信息 (RTTI)dynamic_cast用于多态类型的转换typeidtypeid 运算符允许在运行时确定对象的类型type_id 返回一个 type_info 对象的引用如果想通过基类的指针获得派生类的数据类型基类必须带有虚函数只能获取对象的实际类型type_infotype_info 类描述编译器在程序中生成的类型信息。此类的对象可以有效存储指向类型的名称的指针。type_info 类还可存储适合比较两个类型是否相等或比较其排列顺序的编码值。类型的编码规则和排列顺序是未指定的并且可能因程序而异。头文件typeinfotypeid、type_info 使用#include iostream
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 Planeif(typeid(*obj) typeid(Bird)) // 判断对象类型{Bird *bird dynamic_castBird *(obj); // 对象转化bird-foraging();}obj-land();
}int main(){
Bird *b new Bird();
doSomething(b);
delete b;
b nullptr;
return 0;
}*声明本文于网络整理版权归原作者所有如来源信息有误或侵犯权益请联系我们删除或授权事宜。戳“阅读原文”我们一起进步