类和结构体
不同点:
在结构体外部,结构体内部数据和函数都是公有的,而类内部成员可以规定公有、私有、保护这三种类型。
类继承来的成员的访问权限。P210
类成员的访问权限:类内部可以随便访问任何成员,因此类成员的访问权限是指定了类外部访问类成员的权限,即通过类对象访问类成员的权限。派生类的继承属性规定了派生类外部访问派生类中继承自基类的成员的访问权限,即规定了基类成员在派生类中的属性,如果是私有继承(private),基类成员在派生类中都为私有;如果为公有继承(public),基类成员在派生类中属性维持不变;如果为保护继承(protected),基类成员中,私有成员维持属性不变,公有和保护成员全变为保护成员。注意,在派生类内部,除了不能访问来自基类的私有成员,其他都能访问。
在派生类中,或通过派生类的对象
构造函数
普通变量的初始化比较简单,如inta=1;构造函数的意义就是定义对象是对它进行初始化。
构造函数是类的一个成员函数,特点是,1 没有返回值,2 与类同名,3 创建对象时被自动调用。Clock c(1,2,3),这样就把参数传给了构造函数。
如果在类中没有写构造函数,系统会自动生成一个默认构造函数,没有参数,不做任何事情。
拷贝构造函数
拷贝构造函数的产生动力,是想像复印机一样复印一个对象。
拷贝构造函数仅复制初始值对象的每个数据成员,而不复制函数成员。
拷贝构造函数也是一个构造函数,也有默认的拷贝构造函数,
Base bc1(6);
Base bc2(bc1);一般其实现是
class Base{
public :
Base(Base &p){a=p.a;b=p.b;}
private:
int a;
int b;
}
构造函数
构造函数在以下情况下被自动执行:
1, 建立新对象时;
2, 用new建立对象时
注意,建立指向对象的指针时不会自动执行构造函数,因为建立的不是对象,而是指针变量
析构函数
构造对象时,可能会在构造函数中申请了一些系统资源,如用malloc分配的动态内存,由于动态内存的生存期仅与free函数有关,因此需要在对象的生存期结束前,在系统自动调用的析构函数中释放这些内存。
析构函数
析构函数的特点:1,没有返回值,2,函数名是 ~类名,3,在对象的生存期结束时被自动调用。
多态性
多态是指同样的消息,被不同类型的对象接受时,产生不同的行为。同样的消息是指同样的(准确说是同名的)成员函数调用。
通过下面的例子可以知道,多态其实就是通过基类的指针(或者引用),调用其公共派生类中的成员函数。公共派生类中,有3种类型的成员函数可以被如此方式调用,第一种是从基类继承来的,第二种是与基类中成员函数有相同函数名的,第三种也是与基类中成员函数有相同函数名,与第二种的区别是基类中的同名函数不需要实现,只需要声明函数原型,目的是规定为整个类族提供通用的外部接口语义。第一种通过类型兼容规则实现,第二种通过虚函数实现,第三种通过纯虚函数实现。
纯虚函数和虚函数有什么区别?定义基本一样:virtual disp(参数表)=0;,后面多加了一个=0;虚函数有函数体,而纯虚函数没有函数体,函数体要在派生类中给出;虚函数所在的类可以定义对象,而纯虚函数所在类不能定义对象,叫做抽象类。
1,类型兼容规则例程
Class base1
{
Public:
void Display(){printf(“base1display”);}
}
Class base2 : public base1
{······}
Void fun(base1 *ptr)
{
Ptr->display();
}
void main()
{
base1 a;
base2 b;
fun(b);
}
结果:
base1 display
2,虚函数例程
Class base1
{
Public:
Virtualvoid Display(){printf(“base1 display”);}
}
Class base2 : public base1
{
Public:
Display(){printf(“base2display”);}
}
Class base3 : public base2
{
Public:
Display(){printf(“base3display”);}
}
Void fun(base1 *ptr)
{
Ptr->display();
}
void main()
{
base1 a;
base2 b;
base3 c;
fun(a);
fun(b);
fun(c);
}
结果:
base1 display
base2 display
base3 display
此处注意,虽然base2中没有显式的声明display为虚函数,但是系统自动判断为虚函数,系统进行判断时遵循以下三个条件:
1, 该函数是否与基类中的虚函数有相同的函数名称;
2, ···············相同的参数个数和参数类型;
3,········ 相同的返回值类型。
3,纯虚函数例程
Class base1
{
Public:
Virtualvoid Display(int a,int b) = 0;
}
Class base2 : public base1
{
Public:
void Display(int a,int b){cout<<base2display<<a+b<<endl;}
}
Class base3 : public base2
{
Public:
void Display(int a,intb){cout<<base3 display<<a*b<<endl;}
}
Void fun(base1 *ptr)
{
Ptr->display(2,3);
}
void main()
{
base2 a;
base3 b;
fun(a);
fun(b);
}
结果:
base2 display 5
base3 display 6
此处注意,虽然base2中没有显式的声明display为虚函数,但是系统自动判断为虚函数,系统进行判断时遵循以下三个条件:
1, 该函数是否与基类中的纯虚函数有相同的函数名称;
2, ······相同的参数个数和参数类型;
3, ······相同的返回值类型。
函数成员声明为虚函数后,对该函数进行动态绑定。
绑定就是把一个标识符和一个存储地址(如函数的存储地址)绑定在一起的过程。绑定确定了操作的具体对象。
根据进行绑定的阶段的不同,分为静态绑定和动态绑定。绑定在编译连接阶段完成的称为静态绑定,函数重载就是一种静态绑定;。
继承方式
既然子类继承了父类除了构造、析构函数外的所有成员绑定在程序运行阶段完成的称为动态绑定,那么这些成员就是子类的成员,因此不论哪种方式,子类内部都可以访问这些成员,当然,除了父类的私有成员。那么,继承方式只是规定了子类中从父类继承的成员的访问属性,即类外部通过类对象引用时的访问权限。public继承方式,子类中父类的公用成员和保护成员的访问属性保持不变;protected继承,子类中父类的公共成员和保护成员的访问属性都变成protected方式;私有继承,子类中父类的公共成员和保护成员都变成了private属性,子类内部不可访问。
派生类的构造函数
什么情况下子类必须声明构造函数?当基类的构造函数有形参列表时。因为只有通过派生类声明的构造函数,才能向基类的构造函数传递数据,才能初始化基类的对象。
子类的构造函数仅负责子类新增成员的初始化,继承自父类的成员由父类构造函数初始化。
类对于结构化编程的意义:P130
实例属性、类属性
静态数据成员
一般的类数据成员都必须在定义对象后才能产生,因此其描述的是对象的属性,那么,如果我们要描述一个类的属性,比如一个类的对象的总数,该怎么定义一个变量呢?这就需呀静态数据成员。
在类中声明静态数据成员,如下,
class Base{
staticint a;
};
静态数据成员是类的属性,而其他非静态数据成员是对象的属性。Base的所有对象共同维护和使用该成员,可以当作其作用域是该类的所有对象内部。非静态数据成员的作用域只是一个对象内部。
由于静态数据成员属于类,不属于对象,因此无法通过对象引用,需要通过类名引用。
经验证,静态数据成员既可以通过类名引用,也可以通过对象引用。
引用前必须进行初始化,而且必须在文件作用域初始化,如下,
class Base{
public:
staticint a;
};
int Base::a=3;这样引用是因为静态数据成员不属于对象,属于类本身,因此不能通过对象.数据成员 这样的方式进行引用。
void mian()
{
std::cout<<Base::a<<std::endl;
}
当静态数据成员为私有时,也可以这样初始化,但在main函数中就不能像上面那样访问了
class Base{
private:
staticint a;
};
int Base::a=3;
void mian()
{
std::cout<<Base::a<<std::endl;//错误
}
静态函数成员
由上文对静态数据成员的描述可知,静态数据成员可以在定义对象前就定义而且必须赋初值,如果想通过一个函数输出这个初值,该怎么办呢?可以通过成员函数,但必须在定义对象之后才可以,如果想在定义对象前就输出这个初值呢?那就需要静态函数成员了。
与静态数据成员类似,静态函数成员是类的属性, 访问该函数时可以直接通过类名::函数名访问,也可以通过对象引用。
静态函数成员访问非静态数据成员时,必须通过参数传递方式得到对象名,然后通过对象名引用,但可以直接访问静态数据成员,如下,
class Base{
public:
static int a;
int b;
static void disp(Base sub)
{\
cout<<a<<endl;//right
cout<<b<<endl;//wrong
cout<<sub.b<<endl;//right
};
这是由于只有定义了对象,才有b变量,而未定义对象前就可以通过类名访问该函数。
二维数组与指针
inta[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
a[3][4]是一个一维数组,含有3个元素:a[0]、a[1]、a[2],每个元素是包含4个元素的一维数组。a[0]、a[1]、a[2]是数组名。
因此,a[0]、a[1]、a[2]是这3个数组的首地址。
int *p=a[0];
因此,*p=a[0][0],*(p+1)=a[0][1], *(p+2)=a[0][2]。
a是二维数组的首地址,a指向第一行,a+1指向第二行,a+2指向第三行。如果a=2000,则
a+1=2008,a+2=2016(turbo2/3)。
但是,a+1不应该指向下一个字节么?答:+1并不是地址加1,而是按照a的定义中指向的变量类型,增值。a+1与a的跨度取决于定义a指向什么类型的变量,如果是int *a; a=2000,则a+1=2004,(visual c++6.0规定一个int型占4字节,turbo 2、turbo 3 规定一个Int占2个字节)。
于是,如果我们定义:
int *p=a;
则,p指向a[0]数组,p+1指向a[1]数组,p+2指向a[2]数组,错!!!
那么怎么才能p+1指向下一行呢?这就要引入指向一维数组的指针变量。
指向一维数组的指针变量
以前的指针变量指向的类型,大多是常见的int char等,现在介绍一个指向一维数组的指针变量。
int a[3][4];
int (*p)[4];
p=a;
这样p就指向一个4元素组成的一维数组。
于是,p+1的增值就是这个一维数组长度了。
那么,下面等式成立吗?
p=a[0]=&a[0][0]=第一行第一个元素的首地址;
p+1=a[1]=&a[1][0]=第二行第一个元素的首地址;
p+2=a[2]=&a[2][0]=第三行第一个元素的首地址;
答:不成立,以上三式中用红色标记的等号不成立。应该是下式:
*p=a[0];
*(p+1)=a[1]=&a[1][0]=第二行第一个元素的首地址;
*(p+2)=a[2]=&a[2][0]=第三行第一个元素的首地址;
运算符重载(包含函数重载相关知识)
一般的运算符的操作对象是基本操作类型,如1+2,3*4,如果我们想对类对象进行加减乘除的运算,或者其他运算符的运算,就需要进行运算符的重载。
运算符的重载也就是函数的重载,即对“-”函数的重载。(函数重载即,函数名相同,但形参类型或者和个数不相同,当调用一个函数时,编译器可以根据形参的区别确定到底调用的是哪个函数。)
如果定义类对象:Subc1,c2,c3
c3=c1-c2;等价于c3=c1.– (c2),即c3=c1.operator -(c2),其中,Sub operator -(Sub c)是在类Sub中定义的成员函数,public:Sub operator – (Subc){data-c.data;}
运算符-,实现了类对象之间的减法功能,但仍然保留其原先的功能,如1-2,3-4等。这是函数重载的特性,即使函数名相同,根据传入函数的参数类型、个数的不同,可以调用不同的函数。
函数重载时,如果函数名、参数个数、类型相同,只有返回值不同,可以实现函数的重载吗?
不可以,会提示重定义。
在类中定义运算符重载函数,叫做运算符重载为成员函数;
将运算符重载函数定义为类的友元函数,叫做运算符重载为友元函数。
运算符重载时,运算符左右的操作数和定义运算符函数时形参列表的对应关系是什么?
l 双目运算符:如+ - * %,重载为成员函数时,符号左边操作数对应类对象的数据成员,符号右边操作数对应函数形参;重载为友元函数时,操作数从左到右与形参列表从左到右一致。
l 前置单目运算符:如++a,-a,重载函数必须没有形参,因此操作数通过this指针传入传出。
l 后置单目运算符:如a++,重载函数形参必须为int型,但该形参没作用,只是为了区分前置,操作数也是通过this指针传入传出。
由上文可知,单目运算符只能重载为成员函数,因为友元函数必须由参赛才可以访问。
友元函数与普通函数区别
普通函数和友元函数都可以以类对象为实参,那么两者有何区别呢?
1, 友元函数中可以通过类对象访问类的私有成员和保护成员,而普通函数只能通过类对象访问公有成员。
2, 友元函数要在类中声明,前面要加friend,而普通函数不需要。