C++笔记8.22

不可修饰的对象

const Currency the_raise(42,38)

编译器只知道原型,不知道body并不知道对象的body中的函数会不会修改这个const
所以在函数()后面加const,原型和body都要加

class A{
	int i;
public:
	A():i(0){}
	void f() {cout <<"f"<<endl;}
	void f() const{cout<<"f const"<<endl;}
};
int main(){
	const A a;
	a.f();
	return 0;
}

运行结果显示const,这两个函数构成重载,参数表不同
第一个为f(A this)
第二个为f(const A
this)
const a,以后没办法修改,所以i必须要初始化
或者不是const a,const int i,也必须要初始化

class HasArray{
	const int size;
	int array[size];//error

解决方案:
static const int size 100;
或者枚举

class HasArray{
	enum {size=100}; //是int且不能被改变
	int array[size];
}

引用
对象可以放在堆栈,堆,全局数据区
可以直接访问对象,通过指针访问对象,通过引用访问

char c;
char* p = &c;
char& r = c;

r是一个引用,r是一个变量,是c的一个别名,用r就是在用c
本地或者全局变量 必须有右边的name
在参数表或者成员变量中可以没有右边的初始化(初始化的机会在后面)

int X=47;
int& Y=x;
cout<<y; //输出47
Y=18;
cout<<x;//输出18

Y是X的另外一个名字

引用必须初始化

inx x=3;
int& y=x;
const int& z=x;//z是x的别名,通过z不能修改x,z不能做左值,但是改完x,z自动改了
void f(int &x);
f(y);//在f里头改完之后,外面的值也改了
void func(int &);
func (i *3);//error.有结果但是没有变量名字
int* f(int* x)
{
  (*x)++;
  return x; //safe, x is outside this scope.
}

int& g(int& x) //引用
{
  x++;
  return x;//safe, outside this scope
}
int g(int x)//提示错误,调用时与上面的形式相同,不可以做重载

int x;

int& h()
{
  int q;
  //return q; //wrong  函数返回本地变量地址和指针是错误的
  return x; //ok
}

int main()
{
  int a = 0;
  f(&a); //a=1
  g(a); //x = 2  a被改变了
  *k()=10;
  h() = 16; h()返回结果是引用,既然是引用就可以做左值.
  return 0;
}

具体传参数时,f(&a)括号中的可以看作右值,之前的函数定义可以看作左值
引用不可能再指向别人,不能指向空,指针则相反
而引用是通过const的指针实现
java全都放在堆中,只能通过指针访问,可以把星号去掉
引用不能再被引用
int &a = x;
int &b =y;
b=a;//等价于y=x
但是java的引用可以重新指向
java的指针不能做计算,而C的可以
C++本身看不出来reference是以指针的方式实现的,总是跳转到被捆绑的值

int& *p;//pointer to reference  illegal  因为拿不到reference的地址*p的类型是int的reference
void f(int* &p) //reference to pointer  :ok  p是reference,捆着一个int的指针

c++中&什么意思
变量的前面表示取变量地址赋值给指针, 如:int a = 0; int *pa = &a;
类型后面表示引用,引用即变量的替身。 int a = 0; int &ref = a;操作ref就跟操作a是一样的
还有一种的与预算 如 int a = 0; a &= 0;// 按位与操作
reference不是一个实体,所以没有reference的数组

向上造型
如果B是A的子类,那么B的对象就可以被当作A的对象来使用,子类多出来的东西不看

class A {
public:
	int i;
public:
	A() :i(10) {}
};

class B : public A {
private:
	int j;
public:
	B():j(30){}
public:
	void f() { cout << "B.j" << j << endl; }
};
int main() {
	A a;
	B b;
	cout << a.i << " " << b.i << endl;
	cout << sizeof(a) << " " << sizeof(b) << endl;
	int *p = (int*)&a;
	cout << p << " " << *p << endl;
	*p = 20;
	cout << a.i << endl;
	p = (int*)&b;
	cout << p << " " << *p << endl;
	p++;
	*p = 50;
	b.f();
	return 0;
}

C++笔记8.22_第1张图片
从4和8可以看出,对象里面没有函数,只有成员变量
之所以可以看作父类对象,是因为增加的部分都在后面,只看前面的话,地址偏移和函数都是对的
C中的cast:类型转换,丧失原来的数据
oop中:造型,只是换了一种看待的方法
Manager是特殊的Employee.
Employee : 父类
Manager : 子类

Manager pete(“Pete”,444-55-6666, “Bakery”);
Employee* ep = &pete; // upcasting(向上造型)
Employee& er = pete; //upcasting(向上造型)

原来有2个print,但是由于namehiding,man中只有它自己的print
ep->print 这个时候就是ep的print

多态性
C++笔记8.22_第2张图片

class XYPos { ... }; //x,y point

class Shape
{
  public:
    Shape();
    virtual ~Shape();
    virtual void render(); //加关键字virtual(虚的),表示将来Shape类的所有子类,如果重新写了render,名称一样参数表也相同,那么这个render()和子类里的render()就是有联系的. 
    void move(const XYPos&);
    virtual void resize();
  protected:
    XYPos center;
};
class Ellipse:public Shape
{
   public:
     Ellipse(float maj, float minr);
     virtual void render();//这个virtual可加可不加,若不加,仍然表示virtual.只要祖先说了就行
   protected:
     float major_axis, minor_axis;  //长轴短轴
};

class Circle:public Ellipse
{
  public:
    Circle(float radius):Ellipse(radius,radius) {}
    virutal void render();
};
void render(Shape* p) 
{
  p->render();
}

void fun()
{
  Ellipse ell(10,20);
  ell.render();
  Circle circ(40);
  circ.render();
  render(&ell); //向上造型,
  render(&circ);
}

render函数接受shape的指针来做指针,为通用函数,可用于将来shape的子类的对象
virtual告诉函数通过指针或引用调用函数时,要到运行时才知道调用哪个render,调哪个对象,就用哪个对象的render
p->render();其中p是多态的,p是多态对象
向上造型加动态绑定
静态绑定,编译的时候就知道调用哪个函数
动态绑定,根据指针所指对象来调用,根据p所指对象
取决于render而不是p,因为render是virtual的
void render(Shape* p)
{
p->render();
}

多态的实现
任何类只要有虚函数,它的类就会大一点

class A {
public:
	A() :i(10) {}
	virtual void f() { cout << "A::f()" << endl; }
	int i;
};

int main() {
	A a;
	a.f();
	cout << sizeof(a) << endl;
	int *p = (int*)&a;
	p++;//p++输出的才是10
	cout << *p << endl;
	return 0;
}

最头上自动加上一个隐藏的指针 vptr 表vtable:所有virtual函数的地址,是这个类的,而不是某个对象的

int main() {
	A a,b;
	a.f();
	cout << sizeof(a) << endl;
	int *p = (int*)&a;
	int *q = (int*)&b;
	int *x = (int*)*p;//*p是所指的地方,*p即为那个地方的值 int *把它当作指针
	//p++;//p++输出的才是10
	cout << *p << endl;
	cout << *q << endl;//这两个地址是相同的
	cout << x << endl;//得到很小的值
	return 0;
}

任何一个shape的对象都有指针指向vtable,其中包含了三个函数
ellipse也有自己的vtable,有自己的dtor(编译器写的),有自己的render,resize是沿用的,顺着vptr找到vtable
C++笔记8.22_第3张图片
只用通过指针或引用才是动态绑定


class A {
public:
	A() :i(10) {}
	virtual void f() { cout << "A::f()" <<i<< endl; }
	int i;
};
class B :public A {
public:
	B():j(20){}
	virtual void f() { cout << "B::f()" << j << endl; }
	int j;
};
int main() {
	A a;
	B b;
	A *p = &b;
	p->f();//B的f
	a = b;//把b的值给a,但是a还是a,vptr并没有传递
	a.f();//A的f
	p->f();//B的f
	p = &a;
	p->f();//A的f
	return 0;
}
int main() {
	A a;
	B b;
	A *p = &a;
	int *r = (int*)&a;
	int *t = (int*)&b; //让两个vptr相同
	*r = *t;//让两个vptr相同,即让a的VPTR指向B的构造函数,但是A的vtable里面并没有j的值
	p->f();//b的f,但是j不存在
	return 0;
}
Ellipse elly(20F,40F);
Circle circ(60F);
elley = circ;

circ中只有部分符合的内容传递到elley中
circ的vtable被忽视,elley的vtable就是ellipse的

Ellipse *elly = new Ellipse(20F,40F);
circle* circ = new Circle(60F);
elley = circ;

此时传递没有问题,仅仅是指针重新指向
elley->render就是circle->render

void func(Ellipse& elly){
	elly.render();
}
Circle circ(60F);
func(circ);

引用也没有问题,此时就相当于指针,执行的仍是circ的render

析构函数也需要virtual,做一个父类的指针指向子类对象
Shape *p = new Ellipse(100.0F,200.0F);

delete p;

如果shape的析构不是virtual,则此时是静态绑定,shape的析构会被调用,而子类的被丢掉
如果类里面有一个virtual函数,则它的析构函数也必须是virtual,否则会有麻烦。考虑到万一将来有子类,其他的oop语言默认是virtual函数,而C++静态绑定追求效率,编译时就已经做好

override
父类和子类中的某个函数是virtual的,且名字和参数表相同,则构成覆盖/改写(override)

class Base{
public:
  virtual void func();
}
class Derived : public Base{
public:
	virtual void func();//overrides Base::func()
}

按照下述方法调用被覆盖的函数:

void
Derived::func(){
	cout<<"in deriver::func";
	Base::func;
}

D类从B类继承而来,B类中f返回B的一个指针,Doverride后返回一个D的指针,引用,这样是合法的,但是不能返回子类的对象本身
只有指针和引用才能产生upclass和多态

class Expr{
public:
	virtual Expr* newExpr();
	virtual Expr& clone();
	virtual Expr self();
};
class BinExpr : public Expr{
public:
	virtual BinExpr* newExpr();//ok	
	virtual BinExpr& clone();//ok
	virtual BinExpr self();//error

既有overload又有override,父类有两个virtual的重载函数,子类也必须override这两个函数

引用再研究
引用必须在初始化列表中赋值,否则编译器会error

class X{
public:
	int& m_y;
	X(int& a);
};
X::X(int& a): m_y(a) {}

函数可以返回reference,和返回指针一样,不能返回本地变量,只能返回全局变量,返回reference,写在return的是一个直接的变量,而且是长久的

#include 
const int SIZE = 32;
double myarray[SIZE];
double& subscript(const int i){
return myarray[i];
}
int main()
{ 
   for(int i = 0; i < SIZE; i++)
     myarray[i] = i * 0.5;
   double value = subscript(12);//虽然返回的是引用,看似类型不匹配,但实则把array[12]的值赋给value
   subscript(3) = 34.5;//返回值放在左边做左值,reference当成变量使用,令返回值代表的变量等于34.5
   return 0;
}

传递参数进函数,直接传对象进去,参数的数据太大不如把指针送进去,但是会泄露控制权,因此加上const,几乎是惟一的办法
Person(const string& name);
reference没有那么多星号

void func(const int& y,int& z){
	z= z* 5;//ok
	y += 8;/error,不能做左值
}

void func(int &);
func(i3);//error,i3不能做左值
程序会产生const int tmp@ = i*3;

void f(const int& i)
{
	cout << i << endl;

}
int main() {
	int i = 3;
	f(i * 3);
	return 0;
}

上述程序正确得到结果,因此内部形成了一个匿名的const int变量
去掉const则发生错误

函数的返回是一个const值

class A {
public:
	int i;
	A():i(0){}
};
A f() {
	A a;
	return a;
}
//int g() {
//	int i = 10;
//	return i;   不能写g().i=0,返回的不能做左值
//}
int main() {
	f().i = 10
	return 0;
	A b;
	b.i=20;
	f()=b;//正确
}

上述代码正确,返回的是对象而不是指针,因此可以做左值
若改成const A f() 则错误,因为不能修改,而后续修改了

拷贝构造
bucks
初始化和赋值 initialization和assignment

//: C11: HowMany.cpp
#include 
#include 
using namespace std;

static int objectCount = 0;

class HowMany
{
  public:
    HowMany() { objectCount++; print("HowMany()"); }
    void print(const string& msg = "")
    {
       if(msg.size()!=0)
         cout << msg << ": ";
       cout << "objectCount = " << objectCount << endl;
    }
    ~HowMany()
    {
      objectCount--;
      print("~HowMany()");
    }
};

//Pass and return BY VALUE
HowMany f(HowMany x)
{
  cout << "begin of f" << endl;
  x.print("x argument inside f()");
  cout << "end of f" << endl;
  return x;
}

int main()
{
  HowMany h; //调用构造函数 HowMany():objectCount = 1
  h.print("after construction of h"); //after construction of h: objectCount = 1
  HowMany h2 = f(h); //使用f(h)时候 
                     //begin of f
                     //x argument inside f():objectCount = 1
                     //end of f
                     //HowMany h2调用构造函数
                     //~HowMany():objectCount = 0

  h.print("after call to f()"); //after call to f():objectCount = 0
                                //调用析构函数
                                //~HowMany():objectCount = -1
                                //~HowMany():objectCount = -2
}

h2创建的过程也绕过了构造函数
如果改为Howmany h2=h;最后object = -1,构造了一个新的h2,但是没有经过构造函数
增加构造函数Howmany(int i), 后面的改为Howmany h2(10) 最后object=0,平衡 也可以写成Howmany h2 = 10;初始化变量中圆括号和等号作用
是一样的
在前面增加一个Howmany(const Howmany& o},符合howmany h2=h,恢复成howmany h2=f(h),也达成了平衡 f,x,h2均调用了构造和析构
即为拷贝构造:

T::T(const T&);//不写的话编译器也会自动提供,拷贝每一个成员变量,成员级别上的拷贝

成员有其他类的对象,会调用那个类的拷贝构造,会发生递归
成员里有指针,会有指针的拷贝,指向同一个内存,引用同理
如果传进去的是对象本身,会被不断递归
拷贝构造传reference不会创造出新的对象,因此是安全的
如果复制构造函数是这样的 :

test(test t);
我们调用
test ort;
test a(ort); --> test.a(test t=ort)==test.a(test t(ort))
-->test.a(test t(test t = ort))
==test.a(test t(test t(ort)))
-->test.a(test t(test t(test t=ort)))


就这样会一直无限递归下去。

而运算符=的重载函数参数不一定为引用,但若为引用则可减少一次复制构造函数的调用,有利于提高效率,因此建议运算符=重载函数的参数也为引用。

class Person
{
public:
Person(const char *s);
~Person();
void print();
private:
char *name;
};

//没有写自己的拷贝构造函数
//编译器自己给的拷贝构造,新的对象指针和老的对象指针是相同的.

#ifndef _PERSON_H
#define _PERSON_H
class Person
{
  public:
    Person(const char *s);
    ~Person();
    void print();
    char *name;
};
#include "Person.h"
#include 
using namespace std;

Person::Person(const char *s)
{
  name = new char[::strlen(s)+1];
  ::strcpy(name,s);
}

Person::~Person()
{
   delete [] name;
}
#include 
#include "person.h"

int main()
{
   Person p1("John"); 
   Person p2(p1);
   printf("p1.name = %p\n",p1.name); //p1.name = 0001;
   printf("p2.name = %p\n",p2.name); //p2.name = 0001;
   //p1,p2指向同一段字符串的内存,p1析构了一遍, p2又析构了一遍,释放两次,代码出错. 
   return 0;
}

要用C库中的字符串函数:cstring.h,稍有变化,增加const
size_t strlen(const char *s);
char *strcpy(char *dest,const char *src;);
制造一个新的Person对象,拷贝构造:

Person(const Person& w);
Person : : Person(const person& w){
name = new char[::strlen(w.name)+1];
::strcpy(name,w.name);
}

private是针对类而不是针对对象而言的,同一个类的其他对象可以互相访问类中private的参数

隐藏场景:拷贝构造也会在某些函数被调用时出现,当函数参数是对象,则需要创建一个新的对象

void roster(Person);//声明
Person child("Ruby");
roster(child);

直接场景

Person baby_a("Fred");
Person baby_b = baby_a;
Person baby_c(baby_a);//这两者是一样的,不是assignment

函数需要返回一个对象,返回本地变量给调用的地方

Person captain() {
	Person player("George");
	return player;
}
...
Person who("")
class Person{
public:
	int i;

};
Person f() {
	Person p1;  
	return p1; //需要产生一个临时对象吗

}

int main() {
	Person p = f(); //新的对象被制造出来 是Person p =p1还是 Person @tmp = f();Person p=@tmp;
	return 0;
}

编译器会把不必要的拷贝优化掉

Person copy_func(char *who)
{
   person local(who);
   local.print();
   return local; // copy ctor called!
}

Person nocopy_func(char *who)
{
   return Person(who);
} // no copy needed! 大部分编译器优化掉了,因为这个对象没用过

初始化只能一次,之后全程赋值(前面没有类型定义)

#include 
class Person
{
  public:
    Person(const string&); //char *是C的写法,C++可以把string的拷贝完成得很好,不会有删除两次的问题
    ~Person();
    void print();
    // ... other accessor fxns
  private:
    string name;
    // ... other data members
};

所以这里尽量写string,而不是char *
自觉写好类的default ctor, virtual dtor, copy ctor
不希望被拷贝,声明一个私有的拷贝构造,即别人不可能用你这个对象本身来作为参数传入,不能拿这个对象来构造一个对象

你可能感兴趣的:(C++,c++)