二:类与对象
2.1:类的定义
类定义的语法格式:
class <class-name>
{
//class body
};
类的定义以}作为作用域的结束符,以;作为定义的结束符,为什么以;作为类定义的结束符,因为类与struct结构体一样,可以在定义后直接生成相应的对象,故定义相当于一条C++语句,故以;结束,如下所示:
class test
{
int a;
}g_test;
2.2::类是一个作用域
在结构化程序设计中,作用域不外乎——局部与外部,而引入类后C++源码中形成了一种金子塔式的作用域范围,即全局——类作用域——局部作用域!
//理解类的金子塔式作用域
#include "head.h"
int a=1;//全局作用域,通过::在局部作用域中访问
class test
{
int a; //类作用域通过test::在局部作用域中访问
public:
void show();
test(){a=2;}
};
void test::show()
{
int a=3;//局部作用域
cout<<::a<<" "<<test::a<<" "<<a<<endl;
}
int main()
{
test caodan;
caodan.show();
return 0;
}
类是一种作用域实际上是在说类具有封装性!在面向对象中一般来说通过该类的对象访问类中的成员!
2.3:类是一种数据类型
正如小处着眼一样,面向对象只是在建立一种新型的数据类型!即类是一种数据类型!
2.3.1:存储类与面向对象
A:存储类与对象
在系列二中我们讨论了rase(C++存储类register auto static extern),对于面向对象也同样适用,下面演示了这个过程:
//存储类与对象
#include "head.h"
class test
{
int a;
public:
test(int a){this->a=a;}
}g_test(1); //全局auto对象
static gs_test(2); //全局静态对象
int main()
{
test i_test(3);//局部auto对象
static test s_test(4); //局部静态对象
extern test gd_test;//extern声明引入外部auto对象
cout<<&g_test<<" "<<&gs_test<<" "<<&i_test<<" "<<&s_test<<" "<<&gd_test<<endl;
return 0;
}
test gd_test(5);//外部auto对象
通过程序返回的地址,只有i_test的地址在0x0012FFxx的内存地址中,即只有i_test在栈区,其它对象被分配到堆区,这与系列二中的讨论是一样的!
B:存储类与类
正如上面所说,类是一种作用域,在没有类作用域的结构化程序设计中,函数是通过全局对象来实现共享,在增加类域的面向对象程序设计中,对象如何实现全局共享是一个问题,C++提供了静态数据成员作为该类所有对象的共享成员!为什么选择static作为该类所有对象全局共享成员?这也是类的封装特性所决定的,所谓封装性即将数据结构和算法封装到一个作用域中!当然面向对象依然兼容结构化程序设计中的全局对象方法,同样函数也可以声明为static,下面的程序演示了静态数据成员和静态成员函数:
#include "head.h"
class test
{
static int x;
int y;
public:
test(){y=0x79;}
//static函数只能访问static数据成员,如果cout<<y则错误
static void Cout(){cout<<hex<<x<<endl;}
//非static成员函数可以访问static数据成员与成员函数,即无限制访问
void show(){cout<<hex<<x<<" "<<y<<endl;Cout();}
//static成员函数只能调用static成员函数,如果调用show()则错误
static void showall(){Cout();}
};
int test::x=0x78;
int main()
{
test caodan;
caodan.showall();
caodan.show();
caodan.Cout();
return 0;
}
结论:
1:非static成员函数可以访问static数据成员与成员函数
2:static成员函数只能访问static数据成员或static成员函数
对于static数据成员来说,必须在类内声明在类外定义,对于static成员函数来说,没有this指针,只能访问相应的static成员,不能有c-v限定修饰(在后面讨论)!值得一提只要是类的成员即受访问限定符的影响,相反则不(其实这看上去很傻)!
C:this指针
This指针是默认有C++编译器生成的,起捆绑成员函数与调用该函数的作用,即this指针始终指向当前正在调用函数成员的对象,理解“正在”一词很关键,人脑不论何时,只可能在想一件事,电脑每一个时刻也只能执行一条指令即并发性,所以以面向对象编程思想设计的程序,任何时刻只有一个对象在发挥作用!
//理解this指针
#include "head.h"
class test
{
mutable int a,b;
public:
test(int a,int b){this->a=a;this->b=b;}
test change(test &p)const;
void show()const;
};
void test::show()const
{
cout<<a<<" "<<b<<endl;
}
test test::change(test &p)const
{
p.a+=p.a;
p.b+=p.b;
return *this;
}
int main()
{
test caodan(1,1);
caodan.show();
caodan.change(caodan);
caodan.show();
return 0;
}
友元函数不是类的成员故没有this指针,静态成员因属于所有对象共享的类作用域成员,故无this指针。This指针的显示使用一般有两种情况,一种是显示指明相同名字的变量以确定其不同的作用域,如上面所示的,test(int a,int b){this->a=a;this->b=b;},另一种是返回该对象的引用,如上面的 return *this;!
2.3.2:const与面向对象
在讨论const与面向对象时,我们需要先讨论构造函数!构造函数是一个没有返回值,与类名相同的,起初始化对象数据成员作用的函数!在系列四中讨论函数时,我们提到了函数重载,这种特性在面向对象中也适用,说白了函数重载是一种编译时多态性,这正是面向对象多态性的体现!总结构造函数特点:
构造函数可重载,即构造函数的调用有参数的类型决定!
构造函数有系统自动调用,即构造函数只能为public成员,只有为public成员才可以被系统(外部)调用,才不违背封装特性!下面的程序演示了构造函数的用法:
#include "head.h"
class test
{
int a,b;
public:
test(...){cout<<"默认构造函数接收任何参数!"<<endl;a=b=1;}
//test(void){a=b=2;}
//test(int a,int b=10){this->a=a;this->b;}
test(int a,int b){cout<<"带2参数的构造函数!"<<endl;this->a=a;this->b;}
test(test &p){a=p.a;b=p.b;cout<<"带一个参数的复制构造函数!"<<endl;}
};
int main()
{
test t1("hacker");
test t2;
test t3(1,2);
test t5(t3);
return 0;
}
上面的程序中,有两句注释:
//test(void){a=b=2;}//定义一个不接受任何参数的构造函数test(void)
//test(int a,int b=10){this->a=a;this->b;} //定义一个有初始化表的构造函数
去掉注释编译器会提示我们重复定义,这有助于我们再次理解函数重载,即区别不同的函数,除了函数名外,只有区别形参了,形参的不同有参数的个数和参数的类型决定,这些不同完全取决于传递的实参类型与个数,比如test(…)代表可以授受任何参数,但是当建立test t3(1,2)的对象时,它调用的却是test(int a,int b)函数,即实参的个数和类型决定调用哪个重载函数!
A:const与类
与构造函数相关的是初始化式。即在构造函数后面通过:为分隔符,初始类中的成员。什么要引入这种方式呢?这是因为构造函数本身就是为了初始化类的数据成员,但是在初始化类的数据成员时有一些数据需要提前进行初始化,比如const常量,因此引入了这种方式,同样,即使不是const常量也可以使用这种方法初始化类的数据成员,下面的程序演示了上面的过程:
//初始化式
#include "head.h"
class test
{
int x;
const int y;
public:
test():x(0x78),y(0x79){cout<<"stating constructor........"<<endl;}
void show(){cout<<hex<<x<<" "<<y<<endl;}
};
int main()
{
test caodan;
caodan.show();
return 0;
}
上面的构造函数通过初始化式对数据成员x和y进行初始化!
B:const与对象
修改上面的程序:
#include "head.h"
class test
{
int x;
const int y;
public:
test():x(0x78),y(0x79){cout<<"stating constructor........"<<endl;}
void show(){cout<<hex<<x<<" "<<y<<endl;}
};
int main()
{
const test caodan;
//caodan.show(); error
return 0;
}
上面的注释行是错误的语句,即const对象不能调用非const成员函数!所谓const成员函数是为了防止修改对象而在函数后面添加的const!将上面的void show()修改为void show()const{cout<<hex<<x<<” “<<y<<endl;}程序即可通过编译,如下:
//示例
#include "head.h"
class test
{
int x;
const int y;
public:
test():x(0x78),y(0x79){cout<<"stating constructor........"<<endl;}
void show()const{cout<<hex<<x<<" "<<y<<endl;}
};
int main()
{
const test caodan;
caodan.show();
return 0;
}
三:访问限定
3.1:常规访问限定
如你所看到上面的N多示例程序,在C++中提供了默认的访问限定符——public、protected、private,public成员只要是能看到类的地方都可以访问,protected在基类中相当于私有成员,它和private成员只能被public成员、友元访问!
在继续下面的内容前,我想有两句话朋友们需要理解——第一:任何你看到的文字都是理论,您需要实践。第二:任何规则性的东西都是一种静态的东西,这违背了自然界事物本身最基本的特性——变化,因此不会长久!
常规访问限定是静态的,是死的,那有没有什么方法打破这种常规的访问限定!
3.2:对象无限定的访问类成员——友元
友元可以实现无限定的访问,这有利于将对象的数据结构与算法进行全局处理,突破封装性,即封装性不一定时刻都是好的,所以让友元来实现封装性的完全突破,也可以理解为面向对象与结构化程序设计的结合,从这一点再看C++,不偏不倚不盈不空,无满无不满,终是圆满,再次向b.stroustrup及那些默默无闻的C++“母亲”致敬!
//理解友元函数
//#define FAST
#include "head.h"
class test
{
int x;
public:
test(int x){this->x=x;}
friend void set_value(test &p,int x);
void show();
};
void test::show()
{
cout<<hex<<x<<endl;
}
//对比show()的定义方式即友元不是类的成员
void set_value(test &p,int x)
{
p.x=x;
}
int main()
{
test caodan(1);
set_value(caodan,0x78);
caodan.show();
return 0;
}
//友元类
//#define FAST
#include "head.h"
class fuck;
class test
{
int x;
public:
test(int x){this->x=x;}
friend class fuck;
void show() const{cout<<hex<<x<<endl;}
};
class fuck
{
int y;
public:
void set_value(test &p,int x);
fuck(int y){this->y=y;}
};
void fuck::set_value(test &p,int x)
{
p.x=x;
}
int main()
{
fuck _fuck(0x79);
test _test(0x78);
_fuck.set_value(_test,0x79);
_test.show();
return 0;
}
继承的本质是什么?
继承的本质是基类成员在子类中的访问限定重组。这句话有两点内容,其一继承中子类继承基类的所有成员,其二,子类会对基类的所有成员再次进行访问限定重组,这有继承方式决定,因此继承的语法格式为:
Class derived:access base
{
};
访问限定access有三种方式为public protected private
Public继承:基类的所有public成员和protected成员在子类中的访问限定不变
Protected继承:基类的所有public成员和protected成员都将成为子类的protected成员
Private继承:无继承方式指明时,默认为private继承,基类的所有public成员和protected成员都将成为子类的private成员
因为继承的访问限定重级,所以这一节的标题是半限定方式访问限定!
//理解继承的本质
#include "head.h"
//基类有三个int数据
class base
{
int a;
protected:
int b;
public:
int c;
};
class d1:public base
{
};
class d2:protected base
{
};
class d3:base
{
3.3.2:继承的分类——神奇的倒平行四边形
/\
\/ 我们用一个倒平行四边形来说明继承的分类,从上端到中间位置,一个基类可以派生多个子类,这称为单一继承,从中间到下端,多个基类可以派生出一个子类,这称为多继承,从上端到下端的过程中因为子类会复制所有基类的所有成员,所以最底端的子类会有多个最上端基类的成员,因此这里牵涉到一个继承二义性的问题,即虚基类!这里主要讨论虚基类
//虚基类引入的原因倒平行四边形效应
#include "head.h"
class base
{
public:
int a;
};
class d1:public base
{
public:
int b;
};
class d2:public base
{
public:
int c;
};
class d3:public d1,public d2
{
public:
int sum;
};
//第一种解决方法——通过范围限定符指明其值
#include "head.h"
class base
{
public:
int a;
};
class d1:public base
{
public:
int b;
};
class d2:public base
{
public:
int c;
};
class d3:public d1,public d2
{
public:
int sum;
};