C/C++知识点总结(3)

操作符重载函数(Operator Overload Function)的基本概念:

  • 目的是以与对待内置数据类型相同的方式对待用户自定义类型(程序执行速度会受到影响),限制是不能随意选择函数名和参数个数(必须与重载的基本类型运算符保持一致);

  • 编译器首先检查的表达式中的左操作数是否为对象类型,若是则在其类定义中搜寻对应的operator开头的方法,最后进行函数名字替换,参数类型检查等等操作(作为重载操作符函数的参数,一般使用const的索引类型,除非某个参数需要改变);

  • 如果左操作数为数值类型,则检测右操作数是否为类类型,若是,则在友元函数中寻找对应的重载操作符函数,如果没有相关定义则为错误;如果左右操作数都不是程序员定义类型,则按照基本类型运算符进行操作;

  • 重载操作符函数解决的只是一个可读性问题,也就是让对象类型之间的操作看起来也像内置类型,仅此而已,编译器会将这种类型的操作替换回函数调用的形式

    z=x+y; ->>> z=operator+(x,y);或者z=x.operator+(x,y)


  • 赋值操作符重载函数需要注意三个地方:

    为了不发生内存共享问题,需要重新创建动态内存,并复制传入对象的动态内存;

    为了不发生自我赋值时的内存删除问题,需要检查是否传入对象为当前对象本身;

    为了实现链式表达式,需要将返回值设置为引用对象类型


    1 String& String::operator=(const String& s) {
    2 if(&s == this) return this;
    3 delete str;
    4 len=s.len;
    5 str=new char[len+1];
    6 if(str==NULL) exit(1);
    7 strcpy(str, s.str);
    8 return this;
    9 }

重载操作符函数定义为类成员函数:

  • 由于编译器会自动添加指向当前对象的this指针,所以定义他们的时候代表左操作数的参数可以缺省;

  • 如果我们在类成员函数和全局函数中都定义了针对同一个类型的重载操作符函数,当使用操作符表达式时会产生二义性,编译错误;但使用显示的函数调用则不会有错;较好的解决办法是将重载操作符函数定义为类型的友元函数:


    1 class Complex {……Complex& operator+(const Complex &b) const {……}};
    2 Complex& operator+(const Complex &a, const Complex &b) {……}
    3 Complex a,b,c,d;
    4 c=a+b;//二义性错误
    5 c=a.operator+(b); //正确
    6 c=operator+(a,b); //正确
    7 d=(a.operator+(b)).operator+(c); //正确


  • 如果作为类成员函数出现的重载操作符函数,如果不会修改当前对象的任何类成员变量,则可将函数声明为const(函数的声明和定义两个地方都需要添加);如果某个参数不会在函数内部改变,则良好的设计为加上const参数;

  • 为了支持混合类型运算的隐式转换,转换构造函数为一种解决办法,重载操作符函数为另一种解决办法;

    1 Rational r; r+=4;
    2 Rational(int x);//将4转换为临时匿名Rational对象,然后调用重载操作符函数
    3 void operator+=(Rational &r, Rational &x);
    4 void Rational::operator+=(int x);//r.operator+=(4);
    5 void operator+=(Rational &r, int x);//::operator+=(r,4);

  • 不要对实现了重载运算符函数的数值类的转换构造函数使用explicit;否则在函数调用中试图使用基本类型参数替代该类型的对象的操作就会出错(只能显示转换)

    为了使得某个类对象既可以与类对象进行操作,也可以与基本类型进行操作,可以进行如下设计:

    1 explicit Rational(int x);//转换构造函数
    2 void Rational::operator+=(Rational &r);
    3 Rational a,b; a+=b; a+= Rational(5);

    这样的设计就可以使得r既可以是类对象类型,也可以是内置数值类型;
    作为类成员的重载操作符函数只允许左边的操作数为对象类型,为了使得内置数值类型也可以为左边的操作数,可以进行如下设计:


    1 class Rational {
    2 friend Rational& operator+=(const Rational &x, const Rational &y);
    3 friend Rational& operator+=(const Rational &x, long y);
    4 friend Rational& operator+=(long x, const Rational &y);
    5 };
    6 //全局友元函数
    7 Rational& operator+=(const Rational &x, const Rational &y) {……}
    8 Rational& operator+=(const Rational &x, const long y) {……}
    9 Rational& operator+=(long x, const Rational &y) {……}

    友元函数除了可以访问目标类所有成员之外,与目标类没有任何关系;重载所有可能使用到的操作符函数就可以避免隐式调用类型转换构造函数,加快程序速度;

按值传递对象时容易发生的错误(共享同一份动态内存,多次调用析构函数):

  • 隐式调用拷贝构造函数,其将使用实际参数对象初始化形式参数对象;或者赋值操作符重载函数,使用实际参数对象赋值形式参数对象;(所以如果将拷贝构造函数,或者赋值操作符重载函数显式定义为private函数则可禁止按值传递对象)

  • 当对象内部有成员指针变量指向动态堆内存,如果该对象按值传递,则函数内部的形式参数对象会与实际参数对象共享同一份动态堆内存数据;

  • 形式参数对象被销毁时会调用其析构函数将内部成员指针变量指向的动态堆内存销毁,而这正是与实际参数对象共享的那一份的动态堆内存;

  • 当实际参数对象被销毁的时候会再次调用析构函数试图再次销毁已经被销毁的动态堆内存,此时会发生重复删除内存的错误;

动态管理内存的类设计时,需要考虑如下函数:

  • 缺省构造函数:当提供其他构造函数后,系统不会主动提供缺省构造函数

    Rational(int a=0, int b=1);


  • 拷贝构造函数:系统提供的拷贝构造函数仅为浅赋值,遇到动态内存问题可能出问题

    Rational(const Rational &r);


  • 多个转换构造函数:可以使用多种其他类型初始化本类类型的对象

    Rational(const char *r);

    Rational(int c);

    ……


  • 多个赋值运算符重载函数:使用多种其他类类型对本类类型对象进行赋值,并可以避免在调用赋值运算符重载函数时调用转换构造函数生成适合类型

    Rational& operator=(const Rational& r);

    Rational& operator=(int c);

    ……


  • 析构函数:释放资源

    ~Rational();

数据通信方式(对于C++中不同程序段之间的通信而言,总是使用最低程度的耦合度:首先考虑使用函数内部的局部变量,然后考虑使用类成员变量,其次考虑使用函数形式参数,最后才考虑使用全局变量)

  • 全局变量或者静态变量

  • 方法参数

  • 类成员变量

  • 方法的局部变量

复合对象的创建和销毁:

  • 创建对象时,其构造函数在所有成员变量创建(分配内存,初始化)后才进行调用;并且成员变量的创建顺序是按照其在类定义中声明的先后顺序进行;

    如 果有继承关系,则基类优先于所有派生类进行创建(也就是在调用派生类的构造函数之前进行调用),如果没有在成员初始化列表显示指定调用基类的哪一个构造函
    数,则缺省调用无参构造函数(所以无参构造函数还有这一功能),但是最好使用成员初始化列表显示调用,从而避免不必要的函数调用;


  • C++类定义中不能对基本和类变量进行显示初始化;数值变量保持未初始化,类变量使用无参缺省构造函数进行;而类构造函数是在执行完所有数据成员的构造函
    数之后执行(所以作为成员变量的类对象需要有无参构造函数,否则会发生错误);唯一的例外是static const变量可以在类定义的时候初始化;

  • C++类成员对象有两种设置值的方式:一种是利用无参构造函数初始化,再通过其他方式赋值;另一种是利用成员初始化列表调用指定参数的构造函数初始化;前者效率低下,成员对象被多次设置,后者仅设置一次;

    1 class Rational {
    2 Point p1;Point p2;
    3 Rational(const Point& pp1, const Point& pp2) {p1=pp1;p2=pp2}
    4 };

    上述类定义中,p1和p2会首先调用其无参缺省构造函数进行初始化,然后调用赋值操作符重载函数进行赋值:


    1 class Rational {
    2 Point p1; Point p2;
    3 Point p3&;
    4 const int WEIGHT;
    5 Rational(const Point& pp1, const Point& pp2, Point &pp3, int w):
    6 p1(pp1),P2(pp2),p3(pp3),WEIGHT(w) {}
    7 };

    上述类定义中,p1和p2直接调用其拷贝构造函数进行初始化(从而避免不必要的构造函数调用);成员初始化列表不会改变类成员变量的初始化顺序(由类定义顺序决定),一般不在构造函数声明的地方定义,而是在实现的地方定义;

    如果某个类成员变量为const类型,则必须使用成员初始化列表,否则其不能再赋其他值;如果某个类成员变量为引用类型,则也必须使用成员初始化列表,否则其不能其赋其他值;

    一个类型使用它自身作为类成员变量的时候,不能直接使用Rational
    r;因为类成员变量在类创建之前创建,但此时Rational并没有创建,所以语法错误;但可以使用Rational *ptrr;或者Rational
    &refr;实现对自身类型的引用;

    如果将某个类成员变量声明为static,则即使他们声明为private成员,也可以在类定义之外进行初始化:(可使用类作用域运算符访问,也可使用目标对象访问)
    int
    Rational::count=0; p1.count=9;

    有继承关系的派生类的实例在内存中不仅保存自身的所有非static成员变量,还保存所有基类的所有非static成员变量(包括private,protected和public)


  • 销毁对象时,首先调用对象的析构函数,执行资源释放操作,然后以成员变量初始化创建相反的顺序进行销毁(也就是从类定义中最后一个成员变量开始进行销毁);如果有继承关系,在派生类销毁完毕之后再进行派生类的销毁;

  • 成员初始化列表的目的是避免在复合类构造函数被调用之前,编译器调用成员对象类的缺省无参构造函数(如果没有的话会报错),而在构造函数中又会重新进行赋
    值,所以在成员初始化列表中强制要求调用的构造函数类型。成员初始化列表防止语法错误并改善性能;函数声明中为参数提供的缺省值只能自右向左依次进行,调
    用的时候实际参数赋值的顺序为自左向右依次进行;成员初始化列表使得具有继承关系的类创建更有效率;

调用一个派生类(Drived class)实例的某个函数时,实际调用可能为:

  • 调用的函数来自基类,并在派生类中无定义;

  • 调用的函数来自基类,并在派生类中重新定义(如果函数名和标签完全相同,则为over-write,如果仅函数名相同,标签不同,则为隐藏);

  • 调用的函数来自派生类,基类无定义;

  • 虚函数(涉及多态,指针),基类定义的函数由virtual关键字,并且派生类中有函数标示符完全相同的实现;

C++的继承方式:(不同的继承模式实质上是对基类中成员访问权限的一种修改,只能向可见范围变小的方向修改,也就是说即使使用private继承,也可
以通过public:
Base::publB将基类的成员调整为原始访问权限,但是不能放大权限;这些修改影响子类,子类的客户类和子类的子类。虚拟函数仅在public继承
模式下才有效)

  • public:公共继承:客户在子类中对基类中成员的访问权限与基类保持一致,子类中可以访问父类中的public和protected成员;子类的客户类可以访问子类和基类中的public成员;完全的类扩展;

  • protected:受保护继承:从父类中继承的public和protected成员在子类中变成protected,也就是说子类的客户仅能访问子类
    的public成员,子类对象可访问父类中的public和protected成员,常用于基类的一些方法仅提供派生类访问;

  • private:私有继承:从父类中集成的public和protected成员在子类中变成private,也就是说即使是子类,子类的子类也不能再访问这些父类的成员变量,常用于派生类完全重新定义基类的方法;

派生类中非虚拟函数的查找过程以及函数名隐藏问题:

  • 从当前类定义的类成员函数表开始向基类方向进行搜索,一旦找到对应函数头的函数定义则结束搜索过程;

  • 这个过程中一旦编译器找到函数名相同的函数声明就不再搜索,如果参数列表不匹配则报错而不会继续搜索(即使基类中有其他相同函数名的函数),这就是函数名
    覆盖;(函数名相同的设计方法,对于同一作用域是函数名重载,对于不同作用域是隐藏,也就是同一个类定义与父子类定义中);

  • 如果直到基类都没有找到对应的函数名,则搜索全局函数,以及来自其他文件的extern函数的范围;否则调用失败:

    函数重载仅仅适用于同一类定义中,相同函数名有不同的参数列表,这时候编译器会根据参数列表选择正确的函数。但是在继承关系中,基类和派生类中也可能出现
    这种函数名相同但是参数列表不同的情况,但是这个时候编译器并不会使用重载函数的策略,而是自底向上地搜索函数名相同的函数,一旦找到之后就不再寻找,此
    时如果参数列表不匹配则会报错。这是一种函数名覆盖的机制。这应该和继承关系中的函数重写区别(函数名,参数列表,返回值完全相同),也应该和虚拟函数区
    别(使用virtual修饰的重写函数);解决这个问题的设计如下:


    1 class Account {void deposit(double amount) {……}}
    2 class CheckingAccount: public Account {
    3 void deposit(double amount) {Account::deposit(amount) {……}
    4 void deposit(double amount, double free) {……}
    5 };
    6
    7 CheckingAccount ca;

    当ca实例试图调用Account的deposit(double)方法时,如果CheckingAccount没有定义
    deposit(double),则客户只能调用deposit(double,
    double),父类的deposit(double)被隐藏;因此上述设计在CheckingAccount中定义一个
    deposit(double),然后再调用基类的方法,注意一定需要使用作用域运算符Account::deposit(double),否则调用自 身;


C/C++知识点总结(3),布布扣,bubuko.com

时间: 2024-10-11 17:03:21

C/C++知识点总结(3)的相关文章

H5移动端知识点总结

移动开发基本知识点 一. 使用rem作为单位html { font-size: 100px; }@media(min-width: 320px) { html { font-size: 100px; } }@media(min-width: 360px) { html { font-size: 112.5px; } }@media(min-width: 400px) { html { font-size: 125px; } }@media(min-width: 640px) { html { f

Spring知识点回顾(01)

Spring知识点回顾(01) 一.依赖注入 1.声明Bean的注解 @Component @Service @Repository @Controller 2.注入Bean的注解 @Autowired @Inject @Resource 二.加载Bean 1.xml方式 - applicationcontext.xml : Beans, Bean, Component-Scan 2.注解方式 - @Configuration,@ComponentScan,@Bean 用@Configurati

Javascript易错知识点

? JS易错知识点总结: == 和 === 的区别: ==:判断两个变量的值是否相等. ===:判断两个变量的类型和值是否都相等,两个条件同时满足时,表达式为True. switch中break的作用: 如果一个case后面的语句,没有写break,那么程序会向下执行,而不会退出: 例如:当满足条件的case 2下面没有break时,case 3也会执行 1 var num = 2; 2 switch(num){ 3 case 1: 4 alert('case 1'); 5 break; 6 c

老男孩教育每日一题-2017年5月11-基础知识点: linux系统中监听端口概念是什么?

1.题目 老男孩教育每日一题-2017年5月11-基础知识点:linux系统中监听端口概念是什么? 2.参考答案 监听端口的概念涉及到网络概念与TCP状态集转化概念,可能比较复杂不便理解,可以按照下图简单进行理解? 将整个服务器操作系统比喻作为一个别墅 服务器上的每一个网卡比作是别墅中每间房间 服务器网卡上配置的IP地址比喻作为房间中每个人 而房间里面人的耳朵就好比是监听的端口 当默认采用监听0.0.0.0地址时,表示房间中的每个人都竖起耳朵等待别墅外面的人呼唤当别墅外面的用户向房间1的人呼喊时

JavaScript一些重要知识点结合题目的表现!

function Foo() { //① 声明一个Foo的函数 getName = function () { alert (1); }; return this; } Foo.getName = function () { alert (2);}; ② 为Foo创建一个叫getName的静态属性存储一个匿名函数 Foo.prototype.getName = function () { alert (3);}; ③为Foo的原型对象创建一个叫getName的匿名函数 var getName =

学完了js的知识,一起分享总结知识点

又一个知识点学完了,到了总结学习效果和知识总结的时间了.js这个编程语言相对于html和css的逻辑性要强一些,也比较不容易上手.概念性的知识点不难理解,就是实际的操作并不容易,需要通过学习和借鉴案列来理解和帮助并提高实践操作的能力,把理论知识更好的结合到实践当中去,这样才能更有利于去理解和提高自己,做到知识的真正转化,缺乏理论概念性的支撑,有时真的很难上手,尤其是对于刚学习新手而言.所以需要总结知识点,大家可以互相分享一下学习的方式方法,知识总结,通过这种方式方法,相信可以有效的帮助解决一些学

C#高级知识点概要(1) - 委托和事件

作者:linybo 要成为大牛,必然要有扎实的基本功,不然时间再长项目再多也很难有大的提升.本系列讲的C# 高级知识点,是非常值得去撑握的,不仅可以让你写代码时游刃有余,而且去研究和学习一些开源项目时,也不会显得那么吃力了. 希望大家记住,这里讲的所有的知识点,不仅仅是了解了就可以了,还要会灵活用,一定要多思考,撑握其中的编程思想. 本文讲的是委托和事件,这两个词可能你早就耳熟能详,但你是否真正撑握了呢? 本系列讲的C#高级知识点都是要求开发时能达到可以徒手写出来的水平(不依赖搜索引擎.找笔记等

关于Less,你必知的知识点

这是一篇关于Less学习教程 http://www.maiziedu.com/course/497/,讲解了Less的语法,Less的混合等知识点. 1. 关于 less sass 的预编译处理器 LESS 将 CSS 赋予了动态语言的特性,如 变量, 继承, 运算, 函数. LESS 既可以在 客户端 上运行 (支持IE 6+, Webkit, Firefox),也可以借助Node.js或者Rhino在服务端运行. less 编译使用前期使用koala 编译 2. less的 语法 A): 注

JavaScript 总结几个提高性能知识点

前段时间花时间看了大半的<High Performance JavaScript>这本书啊,然后就开始忙项目了,庆幸最忙的一周已经熬过去了.由于空不出时间,这个月写的学习笔记也不多,忙完最苦X的一周,这两天晚上也算是挑灯夜读了...终于是在残血之际将这本书shut down了... 既然读完了,总归是要学到些什么的.说说对这本书的看法先吧,整体的来说,内容还是不错的,就是感觉有点老了(作为前端小白,也可能是自身水平有限,未能体会到其中真意).看这本书的过程中也是写了挺多代码用以测试的,并且对本

Oracle 相关知识点结构图

最近在学Oracle数据库,制作了些结构图方便记忆!主要涉及到Oracle数据类型,Oracle的表操作以及Oracle的游标,还有的之后再分享...... Oracle 数据类型 因为图片上只能看到结构,一些知识点看不了,建议大家点击这个链接,去看源文件:http://naotu.baidu.com/viewshare.html?shareId=atvuh8jmlb4g Oracle表操作 链接:http://naotu.baidu.com/viewshare.html?shareId=atv