侯捷《C++面向对象编程》笔记第(一)周——超经典

1.推荐书籍

《EFFECTIVE C++改善55个方法》

《STL源码剖析》

《THE C++ STANDARN LIBRARY 》

《C++ Prinmer》

2.头文件与类声明

(1)

类根据里面是否包含指针,分为object based(面向单一class设计)   and  object oriented(面对多重class的设计,classes和classes之间的关系);本课程将分别就两种类型举例,分别写一个complex类(复数),和string类(字符串)。

(2)头文件防卫式声明

防止再次调用头文件的时候,程序重复访问头文件主体

#ifndef __COMPLEX__//第一次没有定义该类型,则定义并访问主体,如果第二次访问,则不需要再访问,可用于所有头文件文件
#define __COMPLEX__

class complex
{
    ......
}

#endif

(3)头文件布局

a. class template(模板)简介(开眼界了)

如果你的类里面的变量类型暂时还没确定,或者说你想随机应变,一会儿是int,下一个又可能是double 等等,可以用模板来泛化定义,使用的时候再定义

template <typename> T

class comples

public:

{
    complex (T r=0, T i=0):re(r), im(i){}
}

private:{ T re, im;}

//main.cpp
complex<double > c1(2.2,4.3);
complex<int > c2(1, 3);

3.构造函数

(1)inline(内建)函数

定义:在class本体内定义的函数称为inline function(但最后是否真的是内联函数,由编译器决定,我们未知,一般太复杂的函数不会被认定为Inline)

(2)访问级别

private:数据或函数部分,不想让外界看到的数据,封装起来;

public:让外界看到。两者可以在一个类body内多次使用

protected:~

(3)构造函数

a.一旦定义,自动启动;

b.构造函数(其他函数也可以)可以有默认值,如果创建的时候未定义,则用default argument;

c.构造函数不需要返回类型;

d.不带指针的类,多半不需要写析构函数;

e.构造函数(其他函数也可以)常常用overloading(重载),用于多种初值设定想法;

class complex
{
public:
    complex(double r=0, double i=0):re(r), im(i) {}//构造函数独有的初始化写法,更大气,等同于在{}里将传值一一赋给参数。这种赋值效率更高
/*
    complex (double r=0, double i=0)
    {re=r; im=i;}
*/
//构造函数常常被重载,虽然函数名在编码者看来看似相同,但在编译器看来是不同的
//如果第一个构造函数已经有默认值,下面这个重载的构造函数是不允许的,因为在新建一个不指定值的类时,编译器会不清楚应该用第一个还是下面这个,因为两个都可以
    //complex (): re(0), im(0) {}
    complex & operator +={const complex &};
    double real() const { return re;}
    double imag() const { return im;}

private:
    double re, im;
    friend complex& _doap1 (complex *, const complex &);
};

//main.cpp

{
    //新建类的几种类型
    complex c1(2,1);
    complex c2;
    complex *p = new complex(4);

}

 

4.参数传递与返回值

(1)ctors 放在private区

存在这种写法,叫Singleton

(2) 常量成员函数

a. 重要!!!在函数后,对于不会改变数据值的函数要加const。如果不加,遇到以下情况,编译器会报错!

class complex
{
public:
    complex(double r=0, double i=0):re(r), im(i) {}
    double real() const { return re;}//函数不会改变数据大小,const位置在函数定义后,在大括号前面
    double imag() const { return im;}//同上

private:
    double re, im;
};
 //main.cpp

{
    complex c1(2,1);
    cout<<c1.real();
    cout<<c1.imag();
    //使用者如上使用没有问题,但是下列使用则会报错
    
    const complex c1(2,1);
    cout<<c1.real();//error!因为使用者想定义一个常量,也就是值不能被改变,但是该句调用时,
//如果原类函数没有加const,那么就说明它可能会改变数据的值,但是我前面又定义了常量,不能改变数据值,
//所以编译器就会报错。解决办法就是在不改变值的函数定义时都加上 const。
    cout<<c1.imag();    
}

(3)参数传递 :pass by value vs. pass by reference (to const)

a. 参数传递时,尽量传引用。引用在底部就是一个指针,所以传值很快。良好习惯:所有传值都尽量用引用。

(32位编译器:指针4个字节,字符1个字节,整型4个字节,double 8个字节,float 4个字节)

b.传参数引用的同时,不想自身的值被改变时,加const

class complex
{
public:
   //...
    complex& operator += (const complex&);//这里的参数传值选择的引用,并且由于不想参数在此函数中
//被改变,所以选择加const.
   
private:
    
};
//main.cpp

ostream&
operator <<(ostream& os, const complex& x)//同理,注意第一个参数未加const,原因后续道来
{
    return os<<'('<<real(x)<<','<<imag(x)<<')';
}

c.return by value vs. return by reference 返回值也尽量传引用

complex& operator += (const complex&); //return by reference

double real() const{ return re; } //return by value

double imag() const{ return im; } // return by value

d.传引用也只是尽量,不一定所有场合都适用,后续会讲。

e.友元。对于其他外界函数,不能调用private成员,但是对于友元函数,就允许用private里的数据

private:
    double re, im;
    friend complex& _doap1 (complex *, const complex &);//友元函数
};

inline complex&
__doap1 (complex * ths, const complex& r)
{
    ths->re += r.re;
    ths->im +=r.im;
    return *ths;
}

f. 相同class的各个objects 互为friends(意思就是类本体里面的函数可以调用private成员,而这样可行的原因就是前面所叙述的那样)

class complex
{
public:
   //...
    int func(const complex& param)
    {  return param.re + param.im; }//该成员可以调用私有变量,是因为其默认为友元函数
private:
    double re,im;
};

g. 总结:

数据要放在private里;

参数尽量以reference传;

是否加const; 返回值也尽量以reference传(允许的情况下);

类的本体里的函数应该加const的要记得加; 

构造函数的初始化的专业写法。

h.补充:class body 外的各种定义:什么情况下可以pass by reference 什么情况下可以 return by reference

不能pass by reference 的情况:函数内部会将变量改变,则不加const;函数不改变变量的时候,则加const;

complex & ___doap1(complex* ths, const complex& r)//第一个变量在函数内部会被改变,所以不加const, 第二个不会被改变,所以加const.
{
    ths->re +=r.re;
    ths->im +=r.im;
    return *ths;

}

不能return by reference的情况:返回值是在函数内部定义的一个变量则不能,因为该变量会随着函数结束而消失。若返回值是已经存在的,则可以返回引用。

complex& ___doap1(complex* ths, const complex& r)//文件名的 & 表明返回的是引用
{
    ths->re +=r.re;
    ths->im +=r.im;
    return *ths;//因为ths是外部已经存在的变量,所以可以返回引用
    //下列情况则不能传引用
    /*
    complex mm;
    mm = ths->re + r.re;
    return mm;//mm会随着函数结束而消失,所以不能传引用
    */

}

5.操作符重载与临时对象

(1)操作符重载-1,成员函数

a. 比如重载 += 操作符,首先我们要知道所有的成员函数一定带有一个隐藏的参数this, 即指向它本身的一个指针,这个指针在函数定义的时候不能出现,但在函数内部可以直接使用,指向的就是调用该函数的那个变量。如 c2 += c1; 则操作符重载中this 指向c2。

inline complex&
complex::operator += (const complex& r)//重载
{
    return __doap1 (this, r);//this 默认指向使用的该符号的变量
}

complex & __doap1(complex* ths, const complex& r)//这个函数也可以直接在上面函数定义,但为防止以后还会继续用,则拎出来单独定义。
{
    ths->re +=r.re;
    ths->im +=r.im;
    return *ths;
}//所有左右形式的符号,都可以这样定义
//main.cpp
complex c1(2,1);
complex c2(2,3);
c2 += c1;//+=操作符需要重载

b. 传递者无需知道接受者是以reference形式接受

complex & __doap1(complex* ths, const complex& r)//开头的& 是接受端
{
    ths->re +=r.re;
    ths->im +=r.im;
    return *ths;//可以看到返回的是指针指向的值,但是接收端是返回的引用,但是我们可以不必在意,这样是可行的
}

c. 对于c3+=c2+=c1的考量,函数返回不能设为void

complex& __doap1(complex* ths, const complex& r)//开始的complex不能为void,如果只是c1+=c2,这样的操作是可以的,c2的值得到了改变,但是如果c1+=c2+=c3这样的操作就会出错,因为c3和c2加完后没有返回值,无法继续和c1相加。
{
    ths->re +=r.re;
    ths->im +=r.im;
    return *ths;
}

e class body之外的各种定义,成员函数(函数前面带类的名称 complex::),全域函数(函数名前面没有类名)

(2)操作符重载-2,非成员函数

a. 为了应对使用者的几种可能做法,对应开发三个函数;

b.再次提醒:以下这些重载函数绝不能return by reference,因为传递的是函数内新建的临时对象;

c.特殊语法(临时对象): typename(  c  );内部也可以加减,相当于定义了一个新的该类型变量并赋值。它的生命到下一行就会结束,并且没有名称。

inline complex
operator + (const complex& x, const complex& y)//重载复数加复数
{
    return complex (real(x)+real(y),
                    imag(x)+ imag(y));//临时对象,生命下一行就会结束,但是因为已经将值return,所以可行。
}

inline complex
operator + (const complex& x, double y)//重载复数加浮点数
{
    return complex (real (x) + y, imag (x));//新建临时对象
}

inline complex
operator + (double x, const complex& y)//重载浮点数加复数
{
    return complex (x+ real (y), imag (y) );//新建临时对象
}

//main.cpp
{
    complex c1(2,1);
    complex c2;

    c2 = c1+c2;//复数加复数
    c2=c1+5;//复数加浮点数
    c2=7+c1;//浮点数加复数
    //......

    //临时对象举例
    complex();//新建一个值为default value的临时对象,下一行就会消失;
    complex(2,1);//新建一个实部为2,虚部为1的临时对象。
}

e. 操作符重载 正号、符号;编译器看参数个数来判断是正号还是加号

f. " << "输出符号的重载,只能作为全局函数来定义"<<"的类型是"ostream"

# include <iostream.h>//该操作符重载必须是全局函数
ostream&//返回不能用void,因为有可能连续调用<<,如式3
operator << (ostream& os, const complex& x)//可以看到第一个参数不加const,因为每传一个值,const的状态就会被改变,所以它不是常量
{
    return os<<'('<<real (x) <<','<<imag(x) <<')';
}
//main.cpp
{
    complex c1(2,1);//1
    cout<<conj(c1);//2
    cout<<c1<<conj(c1);//3
}

 

第一周完!!!

 

posted on 2019-06-16 10:40  Nancy_Fighting  阅读(373)  评论(0编辑  收藏  举报

导航