不可修饰的对象
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;
}
从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
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
只用通过指针或引用才是动态绑定
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
不希望被拷贝,声明一个私有的拷贝构造,即别人不可能用你这个对象本身来作为参数传入,不能拿这个对象来构造一个对象