深度探索C++对象模型-----第一章笔记

第一章:关于对象

1.1 C++模式
 

关于操作符重载的问题

type& operator[](int index)
{
    assert(index < dim && index >= 0);
    return _coords[index];
}
type operator[](int index)const
{
    assert(index < dim && index >= 0);
    return _coords[index];
}

一个类重载operator[]()函数时要重载operator[]两次,一个是const的,一个是非const的。
因为const类型只能访问const约束的函数,而non-const的会优先访问non-const的,如果没有non-const则调用const函数。所以每次重载时定义两个可以保护避免修改需要的变量。

关于友元重载的问题:首先流操作符因为要与cin、cout类交互所以使用friend。

// 声明在函数内部,可以与两个参数交互
friend A operator+(const A &t1, const A &t2);

// 具体实现
A operator+(const A &t1, const A &t2)
{   
    A t;    
    t.a = t1.a + t2.a;    
    t.b = t1.a + t2.b;    
    return t;
}


//非友元重载,只能与一个参数交互,另一个参数则是this指针。
A operator+(A & tmpa)
{
    A t;
    t.a = this->a + tmpa.a;
    t.b = this->b + tmpa.b;
    return t;
}
//实际上,按下面的方式对定义进行修改(交换乘法操作数的顺序),可以将这个友元函数编写为非友元函数:
class1 operator*(double m,const class1 &t)
{
  return t*m;//use t.operator*(m)
}

上图取自别人的博客,大概用法与上述的方法相同。

关于const的问题

#include <iostream>
#include <cassert>
#include <string>
using namespace std;
template <class type, int dim>
class Point
{
public:
Point();
Point(type coords[dim])
{
for (int index = 0; index < dim; index++)
{
_coords[index] = coords[index];
}
}
type& operator[](int index)
{
assert(index < dim && index >= 0);
return _coords[index];
}
type operator[](int index)const
/*这里要注意如果用了operator&会报错const string无法转化为string &形式,具体原因是无法将const传递给普通引用,改法就是const type& operator[](int index)const。这种形式也就是所谓的const *type const this*/
{
assert(index < dim && index >= 0);
return _coords[index];
}
private:
type _coords[dim];
};
template <class type, int dim>
ostream& operator<<(ostream &os, const Point<type, dim> &pt)
{
os << "(";
for (int ix = 0; ix < dim - 1; ix++)
os << pt[ix] << ", ";
os << pt[dim - 1];
os << ")";
return os;
}

const成员函数

classStack
{
public:
    void Push(int elem);
    int Pop(void);
    intGetCount(void) const; // const 成员函数
private:
    intm_num;
    int m_data[100];
};
int Stack::GetCount(void)const
{
    ++ m_num; // 编译错误,企图修改数据成员m_num
    Pop();// 编译错误,企图调用非const函数
    returnm_num;
}

a.const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数.
b.const对象的成员是不可修改的,然而const对象通过指针维护的对象却是可以修改的.
c.const成员函数不可以修改对象的数据,不管对象是否具有const性质.它在编译时,以是否修改成员数据为依
据,进行检查.
d.然而加上mutable修饰符的数据成员,对于任何情况下通过任何手段都可修改,自然此时的const成员函数是可
以修改它的

扫描二维码关注公众号,回复: 4728356 查看本文章

static的问题:存在静态存储区,不能直接调用而是可以采用类名.成员的方式调用。

public class Foo{
    int i=0;
    static{
        int j=i+1;//这里不能调用非static的变量i
    }
    static{
        Foo f = new Foo();
        int j=f.i+1;//通过对象点可以调用
    }
}

linux段管理分为:
BSS段(存储未初始化的全局变量,不占据磁盘空间,存储在其他表中只有当运行时才转为DATA,属于静态内存分配)
DATA段(存储初始化的全局变量)
TEXT段(用于存放程序代码,处理各个源文件之间的函数引用以及处理各个段之间的函数引用问题)
RODATA段(常量区,用于存放常量数据,有时候该区域为节省空间是多个进程共享的,包括const修饰的全局变量等)
STACK段(由系统申请和释放,用于存储参数变量和局部变量,可以处理递归)
HEAP段(由用户申请和释放)
可用nm指令查看一个c文件的内存情况。

class Point{
public:
    Point(float xval);//class object外
    virtual ~Point();//vtbl里
    float x() const;//class object外
    static int PointCount();//class object外

protected:
    virtual ostream& print(ostream &os) const;//vtbl里
    float _x;//class object里
    static int _point_count;//class object外
};
//因此可知class object里只有nonstatic data member和vptr两个,如果此时需要重新增删改_x这个变量,这个类需要重新编译。

虚继承的一个优势在于只有一个虚基类的实例,其他存储的是指针及偏移量。

class A  //大小为4
{
public:
    int a;
};
class B :virtual public A  //大小为12,变量a,b共8字节,虚基类表指针4
{
public:
    int b;
};
class C :virtual public A //与B一样12
{
public:
    int c;
};
class D :public B, public C //24,变量a,b,c,d共16,B的虚基类指针4,C的虚基类指针4
{
public:
    int d;
};

关于静态与非静态的问题:在常用的方法下建议使用静态,静态没有实例对象new出来的开销,只有一份方法。一切不需要实例化的有确定行为的方法都应该设计为静态的。静态方法只能调用静态成员变量。对于用不到this指针的类适合用静态方法。对于Singleton以及工厂模式以及数据库SQLHelper那种模式的,都适合用静态方式。
C++内存5大存储区是堆区、栈区、全局区或静态区、字符常量区、程序代码区。
C++内存分配有三种:静态存储区分配、栈上创建、堆上分配。
栈上存储数据会比堆要来的快,堆是寻找匹配内存的因此会产生碎片而栈并不会产生碎片。

Java也有5个,分别是程序计数器、堆、Java栈和本地方法栈、方法区。
Java静态方法存储在方法区(方法区中有一个常量池,用来存储编译期间生成的各种字面量和符号引用,字符串也存储在其中,但是new出来的字符串在堆中,所有new出来的全部在堆中),C++的静态方法存储在静态存储区。

传值与传址:如果不改变实参则传值,如果改变了实参则传址,当调用数组且不修改实参时建议使用const+指针传递,如果是结构或类则建议用引用传递。建议去看:https://blog.csdn.net/songjunyan/article/details/20576043https://www.cnblogs.com/MATU/p/3819829.html讲得很好。
使用函数进行内存分配时注意:
有时候需要使用二级指针,即指针的指针,例如:

MemAllocate(char *a){
    a=(char *)malloc(sizeof(char));//错误
}

当调用此函数进行内存分配时,发现不能分配内存不能成功,因为此时对于a来说,形参改变了,但实参并不
会改变,他们对应于不同的内存单元。正确的写法应该是:

MemAllocate(char **a){
    *a=(char *)malloc(sizeof(char));//正确
}


1.引用是实参的别名,存储在实参的内存中,如果不使用引用而是一般变量时要给形参分配内存单元也就是实参的副本,如果实参是对象还将调用拷贝构造函数,因此一般而言引用比一般变量传递参数的效率和空间更好。常引用既可以用引用提高程序的效率,又能保证传递给函数的数据值不被改变。

2.引用与多态的关系:
静下心来看完可以收获很多:https://blog.csdn.net/u011939264/article/details/52175504
 

Class A; 
Class B : Class A{...}; 
B b; 
A& ref = b;//1.3节那里详细介绍了三种多态方式


总结下来是基类指针和引用可以指向派生类对象,但是无法访问不存在于基类而只存在于派生类的元素。(因此我们需要虚函数和纯虚函数)。如果要引用派生类里的成员则需要强制进行派生类转化使其静态绑定为派生
类。或者在动态绑定时让派生类重写基类的成员函数就可以访问到派生类了。而动态绑定需要基于虚函数来实
现。
静态类型表明一个对象能执行的所有动作(成员函数)+它的自有属性(数据成员)。
而动态绑定可以修改动态类型为派生类。
只有虚函数会实现动态绑定,而多态也是基于动态绑定从而实现的,也就是class A里的函数为虚函数,这样
基类才可以访问到派生类里的成员。

3.流操作符一定要使用引用为返回值
只有引用才可以实现级联操作,因为如果不使用引用,两次<<操作符产生的结果实际上是针对不同对象的,如
果不是引用,程序返回时就会生成新的临时对象。
ostream& operator<<(ostream& out, const A& a) { out<<A.x<<' '<<A.y<<endl; return out;}
而+-*/不能返回引用,如果返回引用那么引用源会没有。也就是sum会没有。

A operator+(A a,A b) 
{
    A sum;   
    sum.x=a.x+b.x; 
    sum.y=a.y+b.y;   
    return sum; 
}
A& operator+(A& a,A &b);
//也是可以的,但是如果是A& operator+(A& a,B &b);则可能会报错因为如果堆栈已被破坏就会报错。
//注:返回一个引用不能使用临时变量
int & fun() 
{ 
    int a; 
    a=10; 
    return a; 
}//这样是不行的,因为a会在fun退出时被销毁,这时返回的a的引用是无效的。
//这种情况下,如果fun的返回类型不是int & 而是int就没有问题了。
//函数返回时如果不是返回引用那么一定会产生一个临时变量。
#include <iostream>
using namespace std;
class Complex
{
   public:
   friend ostream& operator << (ostream&,Complex&); //声明重载运算符“<<”,以友元形式是因为最后是以cin、cout形式输出的,而cin、cout与Complex是不同的类,因此让cin、cout可以访问Complex。
   friend istream& operator >> (istream&,Complex&); //声明重载运算符“>>”
   private:
   double real;
   double imag;
};
ostream& operator << (ostream& output,Complex& c)
{
   output<<"("<<c.real;
   if(c.imag>=0) output<<"+";//虚部为正数时,在虚部前加“+”号
   output<<c.imag<<"i)"<<endl;  //虚部为负数时,在虚部前不加“+”号
   return output;
}
istream& operator >> (istream& input,Complex& c)  //定义重载运算符“>>”
{
   cout<<"input real part and imaginary part of complex number:";
   input>>c.real>>c.imag;
   return input;
}
int main( )
{
   Complex c1,c2;
   cin>>c1>>c2;
   cout<<"c1="<<c1<<endl;
   cout<<"c2="<<c2<<endl;
   return 0;
}

4.友元函数是在类中用关键字friend修饰的非成员函数,它并不是本类的成员函数但却可以通过对象名访问类的私有成员和保护成员。友元类是在别的类中声明我是你的friend,可以获取那个类中的私有和保护成员。是去别人那里声明,然后访问别人的成员。

1.2关键词带来的差异

主要讲了struct和class,里面的模板:
template<typename T>
inline T const& max(T const& a,T const& b)
{    
    return a<b?a:b;
}

1.3对象的差异

重点:

class Parent{};
class Child:public Parent{};
Child c;
Parent p=c;//派生类的对象可以直接赋值给基类对象
Parent &rc=c;//基类对象的引用可以引用一个派生对象
Parent *pc=&c;//基类对象的指针可以指向一个派生对象

对于多继承会产生二义性的问题,因为多继承会保留有多套基类的数据,因此调用要使Obj.A::next=0去访问
,而虚继承只保留有一套基类的数据。

class L
{
public:
    int next;
};
class A:public L{};
class B:public L{};
class C:public A,public B
{
public:
    void f(){next=0;}
};

C Obj;
Obj.next=0;//会产生二义性的错误
Obj.A::next=0;//可以正常访问。如果是虚继承的话就不用这么麻烦。
zooAnimal za("Zoey");
zooAnimal *pza=&za;//pza是指向za指针。

猜你喜欢

转载自blog.csdn.net/Parallel2333/article/details/83176017
今日推荐