当前位置: 首页 > news >正文

怎么建设网站是什么网站报价方案范文

怎么建设网站是什么,网站报价方案范文,网络营销推广案例有哪些,网站的图片尺寸C学习笔记——从面试题出发学习C C学习笔记——从面试题出发学习C1. 成员函数的重写、重载和隐藏的区别#xff1f;2. 构造函数可以是虚函数吗#xff1f;内联函数可以是虚函数吗#xff1f;析构函数为什么一定要是虚函数#xff1f;3. 解释左值/右值、左值/右值引用、std:… C学习笔记——从面试题出发学习C C学习笔记——从面试题出发学习C1. 成员函数的重写、重载和隐藏的区别2. 构造函数可以是虚函数吗内联函数可以是虚函数吗析构函数为什么一定要是虚函数3. 解释左值/右值、左值/右值引用、std::move、移动语义、完美转发等相关的概念3.1 左值/右值的概念3.2 左值引用/右值引用的概念3.3 std::move的作用3.3 移动语义的概念3.4 完美转发的概念 4. decltype、volatile、explicit、override、mutable关键字的作用4.1 decltype4.2 volatile4.3 explicit4.4 override4.5 mutable 5. 构造函数相关的default和delete关键字的作用6 . extern C的作用7. 解释动态多态和静态多态的区别8. 菱形继承有什么问题如何解决9. 段错误有哪些类型10. 如何定义一个只能在堆上栈上生成对象的类11. delete this 合法吗12. 不同类型智能指针的区别12.1 auto_ptr12.2 shared_ptr12.3 weak_ptr12.4 unique_ptr 13. 不同强制类型转换运算符的区别13.1 static_cast13.2 dynamic_cast13.3 reinterpret_cast13.4 const_cast 14. 如何重载操作符重载操作符的返回值流运算符为什么不能通过成员函数重载16. 如何理解函数指针、类成员函数指针 C学习笔记——从面试题出发学习C C博大精深在学习过程中我也有看过《Effective C》、《Efficient C》、《C Prime》这样一些C的经典大作但是个人感觉是由于语法太多很难抓住重点在工作中如果不很经常用到某个语法即使在书籍上有看过也会很快忘记。而刷面试题是一个很好的查漏补缺的方式本博客将以面试题为切入点将面试题中涉及的语法展开学习以彻底搞懂进而达到在平常的工作中能够灵活运用目的下面就逐个开始语法的学习 1. 成员函数的重写、重载和隐藏的区别 这里我们直接给出三种不同概念的定义 重载指的是同一作用域内例如同为某一个类的成员函数函数名相同入参不同的情况 隐藏指的是不同作用域内例如两个函数分别位于父类和子类中函数名相同入参不同则直接构成隐藏入参相同且非虚函数否则为重写 重写特指两个函数分别位于父类和子类中函数名相同入参相同且为虚函数的情况注意和隐藏做区别 我们要知道 重载是静态多态的表达形式重写是动态多态的表达形式。 除此之外下面这种情况注意隐藏和重写输出的区别 如下是隐藏调用的是Base类中的func函数 #includeiostreamusing namespace std;class Base { public:void fun(int i){ cout Base::fun(int) : i endl;} };class Derived : public Base { public:void fun(int i){ cout Derived::fun(int) : i endl;} }; int main() {Base b;Base * pb new Derived();pb-fun(3);//Base::fun(int)system(pause);return 0; }如下是重写调用的是Derived类中的func函数: #includeiostreamusing namespace std;class Base { public:virtual void fun(int i){ cout Base::fun(int) : i endl;} };class Derived : public Base { public:virtual void fun(int i){ cout Derived::fun(int) : i endl;} }; int main() {Base b;Base * pb new Derived();pb-fun(3);//Derived::fun(int)system(pause);return 0; }2. 构造函数可以是虚函数吗内联函数可以是虚函数吗析构函数为什么一定要是虚函数 首先我们要了解虚函数的基本实现原理虚函数的基本结构是虚表 1虚表是一个指针数组每个元素对应一个虚函数的函数指针普通函数的调用并不需要经过虚表所以虚表的元素并不包括普通函数的函数指针 2虚表内的虚函数的函数指针的赋值发生在编译器的编译阶段也就是说在代码的编译阶段虚表就已经完成构造 3虚表是属于类的而不是属于某个具体的对象一个类只需要一个虚表即可。同一个类的所有对象都使用同一个虚表父类和子类属于不同的类。为了指定对象的虚表对象内部包含一个虚表的指针来指向自己所使用的虚表。从下图我们可以理解类对象、虚表和虚函数的区别 其中类B继承类A类C继承类B。类A有两个虚函数A::vfunc1()和A::vfunc2()类B重写了B::vfunc1()类C重写了C::vfunc2()。当我们使用指针调用虚函数且满足指针向上转型条件时就可以触发动态绑定如下代码 int main() {B bObject;A *p bObject;p-vfunc1(); // 最终调用的时B::vfunc1()这个函数 }了解了虚函数的基本实现原理后我们来回答下上面相关的问题 1构造函数不可以是虚函数因为每个对象中的虚函数的实现依赖虚函数表指针_vptr指向的虚函数表来确定在执行构造函数前对象尚未完成创建虚函数表指针_vptr还不存在也就无法通过虚函数表确定构造函数的具体实现。 2虚函数可以是内联函数内联是可以修饰虚函数的但是当虚函数表现多态性的时候不能内联。 内联是在发生在编译期间编译器会自主选择内联而虚函数的多态性在运行期编译器无法知道运行期调用哪个代码因此虚函数表现为多态性时运行期不可以内联。 3将可能会被继承的父类的析构函数设置为虚函数可以保证当我们创建一个子类然后使用基类指针指向该子类对象释放基类指针时可以释放掉子类的空间防止内存泄漏。而对于不会被继承的类来说其析构函数如果是虚函数就会浪费内存。 3. 解释左值/右值、左值/右值引用、std::move、移动语义、完美转发等相关的概念 3.1 左值/右值的概念 左值是可寻址的变量有持久性例如变量名、返回引用的函数调用、前置自增自减、解引用等 右值是不可寻址的常量或在表达式求值过程中创建的无名临时对象短暂性的例如字面值、返回非引用类型的函数调用、后置自增自减、算数表达式等 左值和右值主要的区别之一是左值可以被修改而右值不能。 3.2 左值引用/右值引用的概念 左值引用引用一个对象其通常用在函数传参或者返回值来避免对象拷贝 右值引用就是必须绑定到右值的引用通过 获得右值引用其主要作用是实现移动语义和完美转发两个概念见下文 这里补充很重要的一点常量左值引用也可以引用右值通常用于入参或者类拷贝构造函数中更全面的关系如下表所示 3.3 std::move的作用 std::move的作用是将一个左值变成右值换一个角度讲就是可以将一个右值引用指向左值。如下 int main() {int a 1;int b std::move(a);std::cout a a std::endl;std::cout b b std::endl;return 0; }输出结果如下 a 1 b 1更具体地std::move的定义如下 templatetypename _Tp constexpr typename std::remove_reference_Tp::type move(_Tp __t) noexcept { return static_casttypename std::remove_reference_Tp::type(__t); }其中remove_reference的含义是获得去掉引用的参数类型从上面定义可以看出std::move()并不是什么黑魔法而只是进行了简单的类型转换 1如果传递的是左值则推导为左值引用然后由static_cast转换为右值引用 2如果传递的是右值则推导为右值引用然后由static_cast转换为右值引用 使用std::move之后就意味着两点 1原对象不再被使用如果对其使用会造成不可预知的后果对int等基础类型进行move()操作不会改变其原值 2所有权转移资源的所有权被转移给新的对象 3.3 移动语义的概念 移动语义是通过移动构造函数和移动赋值构造函数实现的其主要目的是为了避免资源的重新分配。如下面分别定义拷贝构造函数、赋值构造函数、移动构造函数、移动赋值构造函数四种构造函数 class BigObj { public:explicit BigObj(size_t length): length_(length), data_(new int[length]) {}// 析构函数~BigObj() {if (data_ ! NULL) {delete[] data_;length_ 0;}}// 拷贝构造函数BigObj(const BigObj other): length_(other.length_), data(new int[other.length_]) {std::copy(other.mData, other.mData mLength, mData);}// 赋值构造函数BigObj operator(const BigObj other) {if (this ! other;) {delete[] data_; length_ other.length_;data_ new int[length_];std::copy(other.data_, other.data_ length_, data_);}return *this;}// 移动构造函数BigObj(BigObj other) : data_(nullptr), length_(0) {data_ other.data_;length_ other.length_;other.data_ nullptr;other.length_ 0;}// 移动赋值构造函数BigObj operator(BigObj other) { if (this ! other;) {delete[] data_;data_ other.data_;length_ other.length_;other.data_ nullptr;other.length_ 0;}return *this;}private:size_t length_;int* data_; };在移动构造函数和移动赋值构造函数中没有分配任何新资源也没有复制其它资源仅仅是将other的资源进行了移动占为己用而other中的内存被移动到新成员后other中原有的内容则消失因此需要进行重置。当data_大到百万个元素时如果使用原来拷贝构造函数的话就需要将该数百万元素逐个进行复制性能可想而知。而如果使用该移动构造函数因为不涉及到新资源的创建不仅可以节省很多资源而且性能也有很大的提升。 那么我们如何触发移动构造函数和移动赋值构造函数呢也就是触发移动语义呢可以有如下几种场景 场景一 int main() {std::vectorBigObj v;v.push_back(BigObj(10));v.push_back(BigObj(20));return 0; }上述代码中两个push_back()调用都将解析为push_back(T)push_back(T)使用BigObj的移动构造函数将资源从参数移动到vector的内部BigObj对象中。而在C11之前上述代码则生成参数的拷贝然后调用BigObj的拷贝构造函数。 如果参数是左值则将调用push_back(T) int main() {std::vectorBigObj v;BigObj obj(10);v.push_back(obj); // 此处调用push_back(T)return 0; }如果希望调用push_back(T)则需要使用std::move函数 int main() {std::vectorBigObj v;BigObj obj(10);v.push_back(std::move(obj)); // 此处调用push_back(T)return 0; }这里需要注意的是因为调用了std::move因此下文要避免对obj对象做进一步操作否则可能会导致内存越界等问题。 场景二 BigObj fun() {return BigObj(); } BigObj obj fun(); // C11以前 BigObj obj fun(); // C11上述代码中在C11之前我们只能通过编译器优化(N)RVO的方式来提升性能如果不满足编译器的优化条件则只能通过拷贝等方式进行操作。自C11引入右值引用后对于不满足(N)RVO条件也可以通过移动语义避免拷贝进而达到优化的目的。 3.4 完美转发的概念 完美转发的定义指的是函数模板可以将自己的参数“完美”地转发给内部调用的其他函数。这里注意首先一定是模板函数其次完美指的是不仅能准确转发参数的值还能保证转发参数的左、右值属性不变。这个为什么重要呢因为在很多场景中是否完美转发直接决定了该参数的传递过程使用的是调用拷贝构造函数还是调用移动构造函数结合上面移动语义的概念我们应该就很好理解这个对于性能的提升是非常重要的。 首先我们定义一个没有完美转发的functoin函数 templatetypename T void function(T t) {vfun(t); }这里首先需要解释下T的含义C 11规定在模板函数中使用右值引用语法定义的参数来说它表示“万能引用”满足引用折叠规则T – TT – TT –TT – T因此左值参数最终转换后仍为左值右值参数最终转成右值。尽管如此因为形参t是有名字且可取地址的因此其传递到内部后仍然是左值仍然满足不了完美转发的定义。 因此在C 11中就引入了std::forward函数std::forward的定义如下 template typename T T forward(typename std::remove_referenceT::type param) {return static_castT(param); }template typename T T forward(typename std::remove_referenceT::type param) {return static_castT(param); }第一个是左值引用模板函数第二个是右值引用模板函数其中remove_reference的含义是获得去掉引用的参数类型左右值属性不变根据“万能应用”的定义因此左值参数最终转换后仍为左值右值参数最终转成右值。因此如下实现就完成了完美转发 template typename T void function(T t) {vfun(forwardT(t)); }这里补充下在C 11之前是否可以实现完美转发的效果呢上面我们介绍了右值是通过常量左值引用传递的因此通过重载函数模板是可以实现同样功能的如下 //接收右值参数 template typename T void function(const T t) {otherdef(t); } //接收左值参数 template typename T void function(T t) {otherdef(t); }但是相较之下这种实现方式会更麻烦些因此我们通常还是使用std::forward函数实现完美转发。 4. decltype、volatile、explicit、override、mutable关键字的作用 4.1 decltype decltype和auto都是用于推导变量类型但用法不同如下auto varname value; decltype(exp) varname value;其中auto要求变量必须初始化deltype不要求decltype要求推导的对象一定是有类型的但对象可以使一个普通的变量、表达式或者其他任意复杂的形式decltype的经典用法是和priority_queue结合当我们需要对一个自定义类进行堆排序时如果不使用decltype的话写法如下bool cmp (const T lhs, const T rhs) { return lhs rhs; }priority_queueT, vectorT, bool (*) (const T lhs, const T rhs) pq(cmp);如果换成deltype进行类型推导的话代码会显得更加简洁bool cmp(const T lhs, const T rhs) { return rhs lhs; }priority_queueT, vectorT, decltype(cmp) pq(cmp)4.2 volatile volatile的含义是让编译器每次操作变量时一定是内存中取出而不是使用已经存在寄存器中的值主要使用在1中断服务中修改供其他程序检测的变量2多任务环境下各任务间共享的标志应该加volatile注意不是多线程3存储器映射的硬件寄存器通常也要加volatile说明因为每次对它的读写都可能由不同意义。volatile可以使修饰指针用法和const类似volatile并不能解决多线程中的问题具体原因可以参考谈谈 C/C 中的 volatile 4.3 explicit explicit修饰单参构造函数用于说明该构造函数不能进行隐式转换无参或者多参构造函数无隐式转换如下 class People{ public:int age;explicit People(int a){agea;} };void foo(void){People p1(10); //方式一正确People* p_p2new People(10); //方式二正确People p310; //方式三这个会报错如果去掉explicit的话就不会报错。}上述代码中如果没有explicit修饰的话方式三会正确运行此时类成员age会被赋值为10这就是C对单参构造函数规定的一种隐式转换这会导致一些很难被发现的bug因此对于explicit关键字应该是能用则用。 4.4 override 如果父类在虚函数声明时使用了override关键字那么该函数必须重载其子类中的同名函数否则代码将无法通过编译。 override存在的目的是为了避免在重载子类同名函数过程中意外创建新的虚函数的情况如下所示 class Base { public:virtual void Show(int x); // 虚函数 };class Derived : public Base { public:virtual void Sh0w(int x); // o 写成了 0新的虚函数 virtual void Show(double x); // 参数列表不一样新的虚函数 virtual void Show(int x) const; // const 属性不一样新的虚函数 };4.5 mutable mutable 只能用来修饰类的非静态和非常量数据成员而被 mutable 修饰的数据成员可以在 const 成员函数中修改。 在 lambda 表达式的设计中按值捕获的方式不允许程序员在 lambda 函数的函数体中修改捕获的变量。而以 mutable 修饰 lambda 函数则可以打破这种限制如下 int x{0}; auto f1 []() mutable {x 42;}; // okay, 创建了一个函数类型的实例 auto f2 []() {x 42;}; // error, 不允许修改按值捕获的外部变量的值在一个类中应尽量或者不用mutable大量使用mutable表示程序设计存在缺陷 5. 构造函数相关的default和delete关键字的作用 要了解default和delete关键字的作用首先要知道C对于默认构造函数的定义在C中如果用户没有定义构造函数那么编译器会自动生成一系列默认构造函数包括拷贝构造赋值构造移动构造移动赋值构造。delete的用处禁止默认构造函数的生成如下 myClass(const myClass)delete;//表示删除默认拷贝构造函数即不能进行默认拷贝 myClass operatir(const myClass)delete;//表示删除默认拷贝构造函数即不能进行默认拷贝但是一旦用户定义了带参数的构造函数那么编译器就不会再自动生成默认构造函数此时其他构造函数都需要用户来定义。此时可以通过default关键字要求编译器来生成一个默认构造函数 myClass()default;//表示默认存在构造函数6 . extern C的作用 extern “C”的作用主要是为了能够正确实现C代码调用其他C语言代码。加上extern C后会指示编译器这部分代码按C语言而不是C的方式进行编译。C和C语言在编译期一个典型的不同是C支持函数重载因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中例如函数 void fun(int, int) 编译后的可能是 _fun_int_in而C语言并不支持函数重载因此编译C语言代码的函数时不会带上函数的参数类型因此如果不加 extern “C”在链接阶段链接器会从 moduleA 生成的目标文件 moduleA.obj 中找 _fun_int_int 这样的符号显然这是不可能找到的 在C出现之前很多底层的库是C语言写的extern “C”的存在就是为了更好的支持原来的C代码和C语言库。extern “C”的使用方法如下 使用单一语句 extern “C” double sqrt(double);使用复合语句相当于复合语句中的申明都加了extern “C” extern “C” {double sqrt(double);int min(int, int); }包含头文件相当于头文件中的申明都加了extern “C” extern “C” {include cmath }不可以将extern “C”添加在函数内部如果函数有多个申明可以都加extern “C”也可以只出现在第一次申明中后面的申明会接受第一个链接指示符的规则。 7. 解释动态多态和静态多态的区别 动态多态的设计思想是对于相关的对象类型确定他们之间的一个共同功能集然后在父类中将这些共同的功能声明为多个公共的虚函数结构。各个子类重写这些虚函数以完成具体的功能。用户代码通过指向父类指针来操作这些对象对虚函数的调用会自动绑定到实际提供的子类对象上去。因此我们可以总结出来动态多态的三个条件 通过指针来调用函数指针向上转型即定义一个父类指针指向子类对象调用的是虚函数 静态多态的设计思想是对于相关的对象类型直接实现他们各自的定义不需要共有基类甚至可以没有任何关系。只需要各个具体类的实现中要求相同的接口声明。用户把操作这些对象的函数定义为模板当需要操作什么类型的对象时直接对模板制定该类型实参即可。 相较之下动态多态更加灵活适合更复杂的应用场景。静态多态是编译期实现的多态效果更高适用于对性能要求高的场景如UI渲染等。 8. 菱形继承有什么问题如何解决 当Father 类和 Mother 类分别从 GrandParent 继承而来GrandSon 从 Father 类和 Mother 类多继承而来类似于这样的继承方式就会形成菱形结构。菱形结构主要问题是 数据二义性当GrandParent中存在类成员变量m是GrandSon是无法直接调用的m而必须通过域运算符(::)进行区分例如 GrandSon grandSon; std::cout grandSon.Mother::m std::endl; std::cout grandSon.Father::m std::endl;空间浪费GrandSon中会存在两份积累GrandParent的数据 解决菱形继承的方法是虚继承即 class Father : virtual public GrandParent {}; class Mother : virtual public GrandParent {};使用虚继承的基类属于虚基类可以看到虚基类并不是在声明基类时声明的而是在声明派生类时指定继承方式声明的。程序运行时只有最后的派生类执行对基类的构造函数调用而忽略其他派生类对虚基类的构造函数调用。从而避免对基类数据成员重复初始化。因此虚基类只会构造一次。 9. 段错误有哪些类型 所谓的段错误 就是指访问的内存超出了系统所给这个程序的内存空间这里我们粗略的进行一下分类 往受到系统保护的内存地址写数据内存越界(数组越界变量类型不一致等) 我们还可以列举一些需要注意的经常导致段错误的场景 定义了指针后记得初始化在使用的时候记得判断是否为NULL在使用数组的时候是否被初始化数组下标是否越界数组元素是否存在等在变量处理的时候变量的格式控制是否合理等 定位段错误的工具通常时GDB具体定位方式就不在此展开了。 10. 如何定义一个只能在堆上栈上生成对象的类 解答这个问题我们首先要知道生成类对象的方式一共就两种第一种是静态建立即通过构造函数构建栈或者静态对象第二种是动态建立即通过new运算符对象构建堆对象。其中new运算符是先执行operator new()函数在堆空间中搜索合适的内存第二步是调用构造函数使用该内存进行初始化。因此new运算符其实也是间接调用了类的构造函数。 对象只能在堆上建立的类最佳的实现方式如下 class A { protected : A(){} ~A(){} public : static A* create() { return new A(); } void destory() { delete this ; } }; 对于这种实现方式有几点需要解释 对象只能在堆上建立最直接的想法是将构造函数设置私有但是上文也提到new运算符其实也是间接调用了构造函数因此该方法不可取。当对象在栈上建立时编译器会析构函数来确定如何释放内存当析构函数时私有时编译器就无法调用析构函数来释放内存也就不会在栈空间上为对象分配内存因此将析构函数设置为私有即可得到一个只能在堆上建立的类。考虑到类的可继承性因此我们可以将析构函数设置为保护变量这样这个类还是可继承的且只能在堆上初始化。 对象只能在栈上建立的类其实只需要将operator new()设置为私有即可 class A { private : void * operator new ( size_t t){} // 注意函数的第一个参数和返回值都是固定的 void operator delete ( void * ptr){} // 重载了new就需要重载delete public : A(){} ~A(){} }; 11. delete this 合法吗 是合法的但是需要注意如下几点 this指向的对象必须是new出来的不能是new[] 、placement new、栈、全局或者其他方式分配的对象只能是简单的new出来的delete this 一旦被调用相当于该对象不复存在对象下的其他成员函数或者成员对象不得再被调用也不得以任何形式操作该对象包括比较、打印、类型转换不能在析构函数中调用delete this因为delete this会出发析构函数进而造成无限递归。 为了更好理解以上几点这里我们对delete关键字功能进一步拓展delete的过程分为如下两步 p-~Object(); p-operator delete(p);其中 第一步是调用p指向的Object对象的析构函数这一步通常由用户自己定义在析构函数中并不会对当前对象进行内存释放 第二步是调用p对象的内存释放语句如果用户没有实现该方法将调用系统内存释放原语operator delete§左释放该对象内存的动作。指示这个对象消亡前最后的动作。通常用户是不需要override这个函数的如果需要一定要在最后调用系统的operate delete操作释放该对象所占用的内存。下面这段代码就很好地说明了这个问题 class x { public :x(){}~x() {printf(~x()/n);//delete p; //这里若进行此操作则会陷入嵌套}void operator delete(void * ptr) {printf(x::delete()/n);} };void main() {x* pnew x;delete p; //依次调用p的~x()和operator deletedelete p; //不会报错因为operator delete override了系统函数没有进行::operator delete(this)操作。delete p; //同理依然不会报错 }12. 不同类型智能指针的区别 这是一个老生常谈的问题了这里我们再总结下c中一共有四个只能指针auto_ptrshared_ptrweak_ptrunique_ptr其中后三个是c 11支持的auto_ptr已经被c 11弃用。 12.1 auto_ptr c98的方案**c11已经弃用**auto_ptr采用所有权模式因此两个auto_ptr不能同时拥有一个对象。如下做法是错误的会造成多次析构int*pnew int(0); auto_ptrintap1(p); auto_ptrintap2(p);auto_ptr的析构函数中删除指针用得是delete而不是delete[]因此auto_ptr不能管理数组指针auto_ptr被剥夺所有权时再次使用会报错如下做法是错误的int*pnew int(0); auto_ptrintap1(p); auto_ptrintap2ap1; cout*ap1;//错误此时ap1只剩一个null指针在手了12.2 shared_ptr shared_ptr是用来解决所有权共享的问题多个shared_ptr可以指向相同对象对象的资源会在最后一个指针销毁时释放shared拥有成员函数use_count返回引用计数个数 unique返回是否独占所有权swap交换所拥有的对象reset放弃对象所有权会引起原有对象计数减少get返回对象指针可以通过make_shared构造shared_ptr 12.3 weak_ptr weak_ptr是用来解决shared_ptr相互引用时的死锁问题如果两个shared_ptr相互引用即那么这两个指针的引用计数永远不会变为零资源永远不会被释放。 weak_ptr解决上述问题的原理是weak_ptr只可以从另一个shared_ptr或者weak_ptr对象构造且weak_ptr的构造和析构不会引起引用计数的增加或者减少。 weak_ptr不可以直接访问对象的方法要将weak_ptr转化为shared_ptr。如下是weak_ptr应用的典型场景 /*修改将share_ptr互相引用换成weak_ptr,避免死锁问题*/ class B; class A {public:weak_ptrB pb_;~A() {coutA delete\n;} }; class B {public:weak_ptrA pa_;~B() {coutB delete\n;} }; void fun() {shared_ptrB pb(new B());shared_ptrA pa(new A());pb-pa_ pa;pa-pb_ pb;coutpb.use_count()endl;coutpa.use_count()endl; } int main() {fun();return 0; }12.4 unique_ptr unique_ptr实现了独有所有权的语义。unique_ptr是仅能移动的类型拷贝是不被允许的。当unique_ptr被移动时资源的所有权也从源指针转移给目标指针而源指针将被置空。如下是一些基本操作 int main() {// 创建一个unique_ptr实例unique_ptrint pInt(new int(5));unique_ptrint pInt2(pInt); // 报错unique_ptrint pInt3 pInt; // 报错unique_ptrint pInt4 std::move(pInt); // 转移所有权//cout *pInt endl; // 报错pInt为空cout *pInt4 endl;unique_ptrint pInt5(std::move(pInt4)); // 转移所有权 }可以通过make_unique构造unique_ptr 13. 不同强制类型转换运算符的区别 这也是C中非常基本的一个知识点主要有四种强制转换类型运算符const_castreinterpret_caststatic_castdynamic_cast 13.1 static_cast static_cast执行非动态转换没有运行时类型检查来保证转换的安全性通常有如下几种用法 用于类层次结构中父类和子类之间指针或引用的转换进行上行转换把子类指针转换成父类指针是安全的进行下行转换把父类指针转换成子类指针时由于没有动态类型检查所以是不安全的。用于基本数据类型之间的转换如把int转换成char把int转换成enum。这种转换的安全性也要开发人员来保证。把void指针转换成目标类型的指针这种转换是不安全的。把任何类型的表达式转换成void类型。 13.2 dynamic_cast dynamic_cast是唯一一个在运行时进行类型检测的转换可以使用它来检验多个动态对象该转换一般用于含有虚函数的基类和派生类之间。dynamic_cast使用的注意事项如下 dynamic_cast转换符只能用于指针或者引用。dynamic_cast转换符只能用于含有虚函数的类。dynamic_cast转换操作符在执行类型转换时首先将检查能否成功转换如果能成功转换则转换之如果转换失败如果是指针则反回一个0值如果是转换的是引用则抛出一个bad_cast异常所以在使用dynamic_cast转换之间应使用if语句对其转换成功与否进行测试。 13.3 reinterpret_cast reinterpret_cast可以把一个指针转换成一个整数也可以把一个整数转换成一个指针先把一个指针转换成一个整数再把该整数转换成原类型的指针使用该操作符的危险性较高一般不应使用该操作符。 13.4 const_cast const_cast用来修改类型的const/volatile属性这种类型的转换主要是用来操作所传对象的 const 属性可以加上 const 属性也可以去掉 const 属性。 14. 如何重载操作符重载操作符的返回值流运算符为什么不能通过成员函数重载 如下是最经典的复数运算的重载操作 #include iostream using namespace std; class Complex { public:Complex(double r 0.0, double i 0.0) : real(r), imag(i) { }//运算符重载成员函数Complex operator (const Complex c2) const;//运算符-重载成员函数Complex operator - (const Complex c2) const;void display() const; //输出复数 private:double real; //复数实部double imag; //复数虚部 }; 例 复数类加减法运算重载为成员函数 Complex Complex::operator(const Complex c2) const{//创建一个临时无名对象作为返回值 return Complex(realc2.real, imagc2.imag); }Complex Complex::operator-(const Complex c2) const{//创建一个临时无名对象作为返回值return Complex(real-c2.real, imag-c2.imag); }具体说来 重载前缀一元运算符 如果为非静态成员函数声明如下 return-type operator op ();如果为非成员函数声明如下 return-type operator op ( class-type );重载后缀一元运算符递增、递减 当为递增或递减运算符的后缀形式指定重载运算符时其参数的类型必须是 int指定任何其他类型都将产生错误。 重载二元运算符 如果为非静态成员函数声明如下 return-type operator op( class-type )如果为非成员函数全局函数、友元函数声明如下 return-type operator op(class-type, class-type)关于如果要重载 B 为类成员函数使之能够实现表达式 class-type1 OP class-type2其中 class-type1为A 类对象则 OP 应被重载为 A 类的成员函数形参类型应该是 class-type2所属的类型。经重载后表达式 class-type1 OP class-type2相当于 class-type1.operator OP(class-type2) 重载操作符可以以void对象的值或者引用的形式进行返回注意这里的对象指向的是调用对象本身对于全局的二元运算符函数就是左侧实参否则就是*this以下几种情况需要返回调用对象的引用 等号连续赋值-*/ 原因是 允许进行连续赋值防止返回对象的时候调用拷贝构造函数和析构函数导致不必要的开销降低赋值运算符的效率和所有内置类型和标准程序库提供的类型遵循相同的协议 流运算符为什么不能通过成员函数重载的原因是因为通过类的成员函数重载必须是运算符的第一个是自己对流运算的重载要求第一个参数是流对象因此我们通常使用友元来解决问题例如如果我们实现 ostream operator(ostream output) {return output; }这样我们只能使用datacout的形式调用进行调用而如果要实现cout data的形式就需要以如下形式实现 #include iostream using namespace std;class Person {public:Person(string name, int age) : name(name), age(age) {}// 重载输出运算符friend ostream operator(ostream out, const Person p) {out p.name p.age;return out;}private:string name;int age; };int main() {Person p(John, 25);cout p endl;return 0; }16. 如何理解函数指针、类成员函数指针 函数指针和函数名的区别在于他们虽然都指向了函数在内存的入口地址但函数指针本身是个指针变量对他做取地址的话会拿到这个变量本身的地址去而对函数名做取址得到的还是函数的入口地址。 函数指针的定义方式如下 int test(int a) {return a; } int main(int argc, const char * argv[]) {int (*fp)(int a);fp test;coutfp(2)endl;return 0; }函数指针所指向的函数一定要报纸函数的返回值类型、函数参数个数和类型一致我们还可以使用typedef来可以简化函数指针的定义 int test(int a) {return a; }int main(int argc, const char * argv[]) {typedef int (*fp)(int a);fp f test;coutf(2)endl;return 0; }函数指针同样可以作为参数传递给函数还可以构建函数指针数组这些是我们实现回调函数的基础 int test(int a) {return a-1; } int test2(int (*fun)(int),int b) {int c fun(10)b;return c; }int main(int argc, const char * argv[]) {typedef int (*fp)(int a);fp f test;couttest2(f, 1)endl; // 调用 test2 的时候把test函数的地址作为参数传递给了 test2return 0; }构成函数指针数字的方式如下 void t1(){couttest1endl;} void t2(){couttest2endl;} void t3(){couttest3endl;}int main(int argc, const char * argv[]) {typedef void (*fp)(void);fp b[] {t1,t2,t3}; // b[] 为一个指向函数的指针数组b[0](); // 利用指向函数的指针数组进行下标操作就可以进行函数的间接调用了return 0; }对于类成员函数指针和函数指针的区别主要如下 对于指向类成员函数的函数指针引用时必须传入一个类对象的this指针所以必须由类实体调用即使用 . (实例对象)或者 -*实例对象指针调用类成员函数指针所指向的函数*。 对于虚函数, 其地址在编译时期是未知的所以对于虚成员函数取其地址子类父类都是如此所能获得的只是一个索引值即其在虚函数表的偏移位置。 对于静态成员函数和非静态成员函数的变脸的赋值方式是一样的都是ClassName::memberVariable形式但是其声明方式不同 class A{ public://p1是一个指向非static成员函数的函数指针void (A::*p1)(void);//p2是一个指向static成员函数的函数指针void (*p2)(void);A(){p1 A::funa; //函数指针赋值一定要使用 p2 A::funb;}void funa(void){puts(A);}static void funb(void){puts(B);} };类成员函数指针就不仅仅是类成员函数的内存起始地址还需要能解决因为 C 的多重继承、虚继承而带来的类实例地址的调整问题所以类成员函数指针在调用的时候一定要传入类实例对象。因此上面类的调用方法如下 int main() { // 非静态和静态类成员函数指针调用方式对比A a;void (A::*pa)(void);pa A::funa; (a.*pa)(); //打印 AA *b a;(b-*pa)(); //打印 Avoid (*pb)(void);pb A::funb;pb(); //打印 Breturn 0; }我们也可以使用在类中定义好的类成员变量调用 int main() {A a;// p是指向A中非static成员函数的函数指针void (A::*p)(void);(a.*a.p1)(); //打印 A这个看起来或许有些奇怪// 使用.*(实例对象)或者-*实例对象指针调用类成员函数指针所指向的函数p a.p1;(a.*p)(); //打印 AA *b a;(b-*p)(); //打印 Ap a.p2; //error尽管a.p2本身是个非static变量,但是a.p2是指向static函数的函数指针 }
http://www.yayakq.cn/news/1778/

相关文章:

  • 手机网站电话漂浮代码深圳酒店网站建设
  • m版网站开发福州建设注册中心网站
  • 做自媒体那几个网站好点衡东网络推广公司
  • 如何把自己做的网站连上网安徽平台网站建设制作
  • 珠海公司网站设计多语种网站营销
  • 深圳网站建设网站推广的方法朝西村网站建设公司
  • 佛山快速建站哪家服务专业wordpress搬运小红书内容
  • 保定网站模板建站简述seo
  • 做侵权网站用哪里的服务器做定制网站怎么样
  • 昆山网站开发建设公司柳州网站建设推荐
  • 阜宁县网站建设前端微信小程序开发
  • 自助建站免费申请个人网页沧州网站制作教程
  • 苍溪网站建设制作安卓手机网站开发
  • 在凡科做网站编辑同城的网站建设
  • 哪些企业必须用网站网络优化关键词
  • 长沙网站建设网站wordpress theme 插件
  • 网站建设规划方案书网站开发项目可行性
  • 视频网站开发前景如何买网站服务器要多少钱一年
  • 建设银行浙江网站物流网络规划与设计
  • 移动端网站建设的方案唐山网站制作价格
  • ps个人网站的首页界面wordpress滑动插件
  • 深圳做网站(推荐乐云践新)wordpress网站商务通
  • 常见的静态网站开发技术甘肃建设银行网站
  • 云南网站建设天软科技网站开发量
  • 中科建建设发展有限公司网站湖南省工商注册登记网
  • 营销企业网站建设应遵守的原则重庆商城网站建设地址
  • 广州游戏网站建设南宁做网站哪家好
  • 手机网站排名优化江苏中南建设集团网站是多少
  • 甘肃网站备案审核网站开发怎么写
  • 网站空间登录免费商城系统源码