C++知识点(3)

来源:互联网 发布:动漫模板源码 编辑:程序博客网 时间:2024/06/06 09:21

内联函数

内联函数相当于自动地用函数的形式添加进代码,可以提高效率。编译器会对内联函数的参数做安全检查或自动类型转换,而宏定义不会。
内联函数可以访问类的私有成员变量,而宏定义则不能,在类中声明同时定义的成员函数,自动转化为内联函数。

继承机制中对象之间的转换

从派生类向基类的类型转换只对指针和引用类型有效。基类向派生类不存在隐式类型转换,使用dynamic_cast。

虚函数、虚函数表的内存分配

对于无虚函数覆盖的继承:虚函数按照其声明顺序放在虚函数表中;父类的虚函数在其子类的虚函数的前面。
对于有虚函数覆盖的继承:派生类中起覆盖作用的虚函数放在原基类虚函数的位置;没有被覆盖的虚函数依旧。
对于无虚函数覆盖的多重继承:每个父类都有自己的虚函数表;派生类的虚函数被放在了第一个父类的虚函数表中(按照声明顺序排序)。
对于有虚函数覆盖的多重继承:派生类中起覆盖作用的虚函数放在原基类虚函数的位置;没有被覆盖的虚函数依旧。

explicit的作用

防止编译器对单参数的构造函数的隐式转换。

class int_proxy {  public:      int_proxy(int n) : _m(n) {};  public:      int value() const {          return _m;      }  private:      int _m;  }; //test_int_proxy(100);

类构造函数隐式转换的必要条件:
1. 找不到传参类型严格对应的函数
2. 找到传参类型严格匹配的类的构造函数
3. 因为隐式转换构造出的是临时对象,所以不可修改,故触发隐式转换的函数的传参类型必须要使用const修饰

之所以要禁止单参数的类构造函数的隐式转换,是因为隐式转换有时会造成不确定性。

模板特化

通常又有一些特殊的情况,不能直接使用泛型模板展开实现,所以要用到模板特化。一是特化为绝对类型;二是特化为引用,指针类型;三是特化为另外一个类模板。
对于特殊类型, 比如说指针, 其拷贝就和基本数据类型不同.。这时为了处理这种特殊情况就需要类模板特化。

memset、memcpy、strcpy三个函数的区别

1)memset() 函数常用于内存空间初始化,如:char str[100]; memset(str,0,100);

2)memcpy用来做内存拷贝,你可以拿它拷贝任何数据类型的对象,可以指定拷贝的数据长度;例:char a[100],b[50]; memcpy(b, a, sizeof(b));注意如用sizeof(a),会造成b的内存地址溢出。

3)strcpy就只能拷贝字符串了,它遇到\0就结束拷贝;例:char a[100],b[50];strcpy(a,b);如用strcpy(b,a),要注意a中的字符串长度(第一个‘\0’之前)是否超过50位,如超过,则会造成b的内存地址溢出。

C++程序的内存分配

栈区(stack)—由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆区(heap)— 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS 。是用链表来实现的。
全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。
文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
程序代码区—存放函数体的二进制代码。

常见的内存错误

内存分配不成功

常用的解决办法是,在使用内存之前检查指针是否为NULL,如果p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,就应该用if(p==NULL)进行防错处理。

内存分配了,但尚未初始化

无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可以省略。

内存分配了,初始化了,但是操作越界

忘记释放内存,造成内存泄漏

含有这种错误的函数每被调用一次就丢失一块内存,最终导致内存耗尽。
动态内存的申请与释放必须配对。

释放了内存却继续使用它

  1. 函数的return语句写错了,注意不要返回指向栈内存的指针或者引用,因为该内存在函数体结束时就被自动销毁了。
  2. 使用free或delete释放了内存后,没有将指针设置为NULL,导致产生“野指针”。

栈区和堆区的区别

栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M,如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

管理方式

栈是编译器自动管理,无需我们手动控制;堆得释放工作由程序员控制,容易产生内存泄漏。

空间大小

堆是用链表实现的,所以几乎没有内存大小限制。栈的大小只有2M。

碎片问题

对于堆来说,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片。
但栈保证了内存块总是先进后出地使用的,不会存在内存碎片的问题。

生长方向

栈是向下生长,是向着内存地址减小的方向增长的。
堆是向上生长,是向着内存地址增加的方向增长的。

分配效率

栈是机器系统提供的数据结构,有专门的寄存器和指令提供对它的支持;堆则是C++函数库提供的,效率低很多。

删除迭代器

对于vector和string,指向其删除点之后位置的迭代器、引用和指针都会失效。
对于deque,删除除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。
对于list,指向容器其他位置的迭代器,引用和指针仍有效。对于插入操作,影响与删除操作一样。

vector中的reserve函数和resize函数

reserve并不改变容器内实际元素数量,它仅影响vector预先分配多大的内存空间,而且只有当需要的内存空间超过当前容量时,reserve调用才会改变vector的容量,此时一般将容量扩充1倍。
resize只改变容器中元素的数目,而不是容器的容量。

sizeof和strlen

(1)sizeof是算符,strlen是函数。
(2)sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以”\0”结尾的。

sizeof在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小。实际上,用sizeof来返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系。strlen在运行时计算,返回字符串的长度。该字符串可能是自己定义的,也可能是内存中随机的,该函数实际完成的功能是从代表该字符串的第一个地址开始遍历,直到遇到结束符NULL。

缓冲区溢出漏洞

使用像strcpy这类不安全函数,这种函数对用户的输入不作任何检查,分配的内存空间是有限的,如果输入超长的字符串,必然会导致溢出。
缓冲区溢出中,最为危险的是堆栈溢出,因为入侵者可以利用堆栈溢出,在函数返回时改变返回程序的地址,让其跳转到任意地址,带来的危害一种是程序崩溃导致拒绝服务,另外一种就是跳转并且执行一段恶意代码。

C++11的新特性

1、 初始化列表应用更广泛2、 auto的引入,foreach循环3、 ​​​​​​​C++ 11 中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。4、 C++11 还增加了防止基类被继承和防止子类重写函数的能力.这是由特殊的标识符final来完成的5、 C++11 通过引入一个新的关键字nullptr充当单独的空指针常量,可以隐式转换任意类型的指针或指向成员的指针的类型,6、 constexpr:constexpr可以说是对const变量的扩展,它只是约束const变量的右侧的式子必须能在编译时就能算出值

STL内存分配

当用户用new构造一个对象的时候,其实内含两种操作:1)调用::operator new申请内存;2)调用该对象的构造函数构造此对象的内容。
当用户用delete销毁一个对象时,其实内含两种操作:1)调用该对象的析构函数析构该对象的内容;2)调用::operator delete释放内存。

迭代器和指针

迭代器返回的是对象引用而不是对象的值,不能直接输出迭代器自身的值。
指针能指向函数而迭代器不行,迭代器只能指向容器。
指针是迭代器的一种。指针只能用于某些特定的容器;迭代器是指针的抽象和泛化。所以,指针满足迭代器的一切要求。

printf参数的执行顺序

printf()函数的参数,在printf()函数读取时是从左往右读取的,然后将读取到的参数放到栈里面去,最后读取到的就放在栈顶,处理参数的时候是从栈顶开始的,所以是从右边开始处理的。

STL

vector 有insert操作,erase操作,底层容器是数组
list 底层容器是双向链表,push_back,push_front ,empty
deque 底层数据结构是一个中央控制器加缓冲区,用堆来保存
stack 底层容器是list或deque
queue 底层容器是list或deque
priority_queue 底层容器是vector,以heap来管理
set,map,multimap,multiset 底层用红黑树
hash_set,hash_map用哈希表

为什么C++不支持内联成员函数为虚函数?

inline函数和virtual函数有着本质的区别,inline函数是在程序被编译时就展开,在函数调用处用整个函数体去替换,而virtual函数是在运行期才能够确定如何去调用的,属于动态绑定。因而inline函数体现的是一种编译期机制,virtual函数体现的是一种运行期机制。此外,一切virtual函数都不可能是inline函数。

为什么C++不支持静态成员函数为虚函数?

这也很简单,静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,他也没有要动态邦定的必要性。

为什么C++不支持友元函数为虚函数?

因为C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。

Operator类型强制转换成员函数

类型转换操作符(type conversion operator)是一种特殊的类成员函数,它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,在保留字 operator 之后跟着转换的目标类型。转换函数又称类型强制转换成员函数,它是类中的一个非静态成员函数。它的定义格式如下:

class <类型说明符1>    {     public:      operator <类型说明符2>();      …    }

这个转换函数定义了由<类型说明符1>到<类型说明符2>之间的映射关系。可见,转换函数是用来将一种类型的数据转换成为另一种类型。
类型转换函数主要有两类:

operator用于类型转换函数

类型转换函数的特征:
1) 型转换函数定义在源类中;
2) 须由 operator 修饰,函数名称是目标类型名或目标类名;
3) 函数没有参数,没有返回值,但是有return 语句,在return语句中返回目标类型数据或调用目标类的构造函数。

对象向基本数据类型转换

class D{public: D(double d):d_(d) {} operator int() const{  std::cout<<"(int)d called!"<<std::endl;  return static_cast<int>(d_); }private: double d_;};int add(int a,int b){ return a+b;}int main(){ D d1=1.1; D d2=2.2; std::cout<<add(d1,d2)<<std::endl; system("pause"); return 0;}

在add(d1,d2)函数调用时隐性地调用了operator int() const 函数。

对象向不同类的对象的转换

class X;class A{public: A(int num=0):dat(num) {} A(const X& rhs):dat(rhs) {} operator int() {return dat;}private: int dat;};class X{public: X(int num=0):dat(num) {} operator int() {return dat;} operator A(){  A temp=dat;  return temp; }private: int dat;};int main(){  X stuff=37;  A more=0;  int hold;  hold=stuff;  std::cout<<hold<<std::endl;  more=stuff;  std::cout<<more<<std::endl;  return 0;}

operator 用于操作符重载

操作符重载的概念:
将现有操作符与一个成员函数相关联,并将该操作符与其成员对象(操作数)一起使用。
操作符函数的一般格式:
return_type operator op(argument list);
return_type:返回类型(要得到什么)
op:要重载的操作符
argument list:参数列表(操作数有哪些)

模板的优缺点

模板的优点

编写一个模板,就可以在实例化的时候由一个模板解决不同类型数据所产生的相同问题。比如说排序问题,你可以给int数据排序和char类型数据排序,没有引入类模板,就需要编写两次排序函数。而引入类模板之后,就可以在实例化的时候,根据不同的数据类型实例化排序方法,做到一模板多用的作用,即多态。

缺点

  1. 模板是一种编译期间生成代码的行为,无法进行断点调试,所以很容易产生bug;
  2. 模板的数据类型只能在编译时才能被确定。因此,所有基于模板算法的实现必须包含在整个设计的头文件中。大量使用模板会造成代码空间膨胀,极大的延长了编译时间。
原创粉丝点击