C++笔记 第3课 类
##类(class)的定义
- 类是将数据和相应对这些数据的操作函数进行封装,并设置访问权限。class不同于struct、union,struct、union是纯数据类型,但不包括函数(操作)
- 类是对象的抽象,而对象是类的实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板 (模子)。
- 类可以继承和派生,并引出了访问权限控制和调整、保护成员、成员函数名静态和动态束定(虚函数)等一系列问题。
//类 举例
class Tdate
{
public:
//可以把成员函数直接在类说明中定义,这样成员函数若符合条件,将自动成为内联函数
void set(int d, int m, int y)
{ day=d; month=m; year = y; }
int isLeapYear( )
{ return(year%4==0 && year%100!=0) || (year%400==0);}
void display( )
{ cout <<month<< ":"<<day<< ":"<<year<<endl; }
private:
int day, month, year;
};
构造函数(constructor) 析构函数(destructor)
- 类建立后往往需要初始化。
- 构造函数是与类同名的成员函数。
- 没有定义构造函数编译器会自动生成一个构造函数(公有构造函数)。
- 与类同名带前缀 ~ 的成员函数被称为是析构函数。
- 构造函数/析构函数不能在程序中显式地直接被调用,而是在对象创建和撤消时隐式地自动被调用。
- 构造函数可以多个同名(函数重载),以达到各种初始化的目的。
- 同名的构造函数必须在类的public声明中逐个列出——如果想定义对象而不赋初值,必须再加一个无参构造函数。
- 构造函数/析构函数没有返回类型。
- 析构函数执行顺序与构造函数执行顺序相反,一一对应。
- 也可以把构造函数改写成带缺省参数的函数:如
Myclass(int i=0);
。
class Circle
{
public:
Circle(double xx = 0, double yy = 0, double rr = 0) : x(xx), y(yy), r(rr) { cout << "Circle(double, double, double) called\n"; }// :x(xx) 的写法效率高一些
~Circle() { cout << "~Circle() called\n"; }
void xSetValue(double xx) { x = xx; }
void ySetValue(double yy) { y = yy; }
void rSetValue(double rr) { r = rr; }
void display() const
{
cout << "O:(" << x << ',' << y << ")" << '\t' << "radius=" << r << '\n';
}
void getArea() const
{
cout << "area=" << 3.14 * r * r << '\n';
}
private:
double x;
double y;
double r;
};
内联构造函数
- 可以通过把构造函数前加inline,使之成为内联构造函数
//类中
public:
Myclass(int i=0);
//函数定义
inline Myclass::Myclass(int i)
{
member = i;
cout << "constructor called "<<member <<endl;
}
- 可以通过说明时把构造函数体直接放在类内,使之成为内联构造函数。类的构造函数体或成员函数体放在类内,则这个函数将自动成为内联函数(当然前提是:此函数符合内联函数的限制条件)。目的:提高执行效率。
复制构造函数
- 复制(拷贝)构造函数是一种特殊的构造函数,其形参只有一个且是本类对象的引用。
- 作用是:使用一个已经存在的对象(由复制构造函数的参数指定),去构造并初始化同类的一个新对象。
class Point
{
public:
Point(int xx = 0, int yy = 0){x = xx; y = yy;}
Point(Point &p){x=p.x;y=p.y;}
private:
int x,y;
}
int main()
{
Point A(4,5);
Point B(A);
}
浅复制与深复制
- 浅复制:复制指针时将地址直接复制,使得不同结构体/对象的指针指向一块内存
- 深复制:重新申请一块内存,再将原来的数据拷贝一份放在新申请的内存里。
- 在声明一个类时,如果不写复制构造函数,编译器会自动为此类生成一个复制构造函数,但这种自动生成的复制构造函数一般都是浅复制,只是把一个对象中的各个成员一一对应赋值给另一个对象的各个成员,包括指针成员的值。这会使得两个对象的指针成员指向同一块动态内存,往往会造成析构对象执行析构函数释放动态内存时,产生致命错误。
常成员函数
不修改类中任何成员值的函数,可以定义为常成员函数。
在函数名( )后加上修饰符const,可确保此函数中不会有任何修改类的成员的行为,否则编译时就会出现错误信息。
void display() const
{
cout << "O:(" << x << ',' << y << ")" << '\t' << "radius=" << r << endl;
}
mutable关键字
在声明类中数据时使用mutable关键字,使得该数据在常成员函数中可以被修改。
如:使用mutable int count;
在类中创建一个计数器变量。
静态成员
同一个类中的不同对象之间共享一个数据,可以用来传递信息。
eg:private: static int num;
静态成员初始化必须在主程序和类外
eg:类外int Student::num = 0;
只有常静态成员才能在类中赋值
eg:static const int num = 0;
#include <iostream>
#include <stdlib.h>
using namespace std;
class Student
{
public:
int age = 18;
static int count;
const float pi = 3.14159;
static const int num = 100;
void agePlus() { age++; }
void countPlus() { count++; }
// void piPlus(){pi++;}//有const就无法修改
// void numPlus(){num++;}//有const就无法修改
};
int Student::count = 2; //若不在类外赋值则无法使用
int main()
{
Student s1, s2;
cout << s1.age << endl; //18
cout << s1.count << endl; //2
cout << s1.pi << endl; //3.14159
cout << s1.num << endl; //100
// cout<<Student.age<<endl;//不允许使用类型名调用数据
// cout<<Student.count<<endl;//不允许使用类型名调用数据
// cout<<Student.pi<<endl;//不允许使用类型名调用数据
// cout<<Student.num<<endl;//不允许使用类型名调用数据
// 同样不可以使用类名来调用函数
// cout << s1.pi++ << endl;//不可修改
// cout << s1.num++ << endl;//不可修改
cout << s1.age++ << endl; //18
cout << s1.count++ << endl; //2
cout << s1.age++ << endl; //19
cout << s1.count++ << endl; //3
cout << s2.age << endl; //18
cout << s2.count << endl; //4
system("pause");
}
友元
让不是本类成员函数的其它函数也能直接访问本类的私有成员方法是:在类内逐个列出其友元函数,每个函数说明前面加上关键字friend
。每一个友元都必须在此类中声明,这有利于此类对访问权限的控制。
/*
编程实现:定义Boat与Car两个类,两者都有weight成员表示重量,
并为每个类设计构造函数(可赋初值也可以不赋初值),
设置新值函数Set, 打印成员值函数Print。
再定义两个类共同的一个友元函数TotalWeight(Boat B, Car C),计算B、C两者的重量之和。
*/
#include <iostream>
#include <stdlib.h>
using namespace std;
class Boat;
class Car;
void outputTotalWeight(const Boat &boat, const Car &car);
class Boat
{
public:
Boat(double ww = 0) : weight(ww) {}
~Boat() {}
friend void outputTotalWeight(const Boat &boat, const Car &car);
void weightSetValue(double ww) { weight = ww; }
void display() { cout << "boat.weight=" << weight << endl; }
private:
double weight;
};
class Car
{
public:
Car(double ww = 0) : weight(ww) {}
~Car() {}
friend void outputTotalWeight(const Boat &boat, const Car &car);
void weightSetValue(double ww) { weight = ww; }
void display() { cout << "car.weight=" << weight << endl; }
private:
double weight;
};
void outputTotalWeight(const Boat &boat, const Car &car)
{
cout << "The total weight is " << boat.weight + car.weight << "." << endl;
}
int main()
{
Boat boat(1);
Car car(2);
outputTotalWeight(boat, car);
system("pause");
}
嵌套类
class enclose
{
public:
class inner
{
public:
void innerFunc();
private:
int innerData;
};
void encloseFunc();
private:
int encloseData;
};
类inner被称为类enclose的嵌套类,受类enclose的作用域限制,另一个类中也可以嵌套一个同名的inner类,也可以直接定义一个同名的inner类,这些inner类相 互之间既无影响也无任何关联。
现在要想使用inner类,必须加前缀enclose::
- 例如,在类外定义inner的成员函数,必须写成:
void enclose::inner::innerFunc()
- 而想用inner类定义对象,必须写成:
enclose::inner innerObject;
类的向前引用
与函数的向前引用类似(有些编译器不需要)
class B;
class A
{};//其中使用了类B
class B
{};
this指针
- this指针只能在成员函数内使用。
- 类的静态成员函数中无this指针。
- this指针不能被更新。
类的组合
类的数据成员时另一个类的对象(在已有的类基础上抽象实现更复杂的类)
例如类class triangle
的成员为三个class point
的对象
指向类的静态成员的指针
int *p = & Student::count;
可以直接通过指针访问公有静态数据成员
指向静态成员函数的函数指针
//class中的public静态成员函数:
static void display(){cout...;}
//main()函数
void(*pFunc)() = Student::display;
pFunc( ); // 相当于执行了display()这一函数
类与对象数组
Student group[3] = {Student(17,甲),Student(18,已),Student(19,丙)};//定义“三人行必有我师焉”小组
类与动态对象
/*
2. 构建一个球类Ball,其成员为球心坐标(x,y,z)和球半径r,
并设计构造函数(可以赋初值也可以不赋初值),复制构造函数,析构函数(打印信息,表示其被调用),
设置新值成员函数Set( ), 取球心坐标成员函数GetX( )、GetY( )、GetZ( ),
取球半径成员函数GetR( ),打印成员值成员函数Print(),计算球体积成员函数Volume()。
并用此类分别定义一个长度为10的静态对象数组,一个长度为20的动态对象数组,
每个对象的球心坐标(x,y,z)和球半径r由随机数rand()产生(球半径r的值应该为正数),
打印每个球对象的(x,y,z)、r和体积;
然后分别将对象数组按照其r值从小到大排序,并打印排序后每个对象的(x,y,z)、r和体积;
并计算打印出所有对象的平均(x,y,z)、平均r和平均体积。
(并释放动态对象数组,注意观察析构顺序与构造顺序的差异。)
//随机数样例
#include <stdlib.h>
void main()
{
int i, x, y, z, r;
srand(0); // srand为随机数序列赋初值,可以随意给初值
for (i = 0; i < 10; i++)
{
x = rand();
y = rand();
z = rand();
r = abs(rand());
}
}
*/
#include <iostream>
#include <stdlib.h>
using namespace std;
class Ball
{
private:
double x, y, z;
double r;
public:
Ball(double xx = 0, double yy = 0, double zz = 0, double rr = 0) : x(xx), y(yy), z(zz), r(rr) {}
Ball(Ball &b) : x(b.x), y(b.y), z(b.z), r(b.r) {}
~Ball() { cout << "~Ball() called.\n"; }
void xSet(double xx) { x = xx; }
void ySet(double yy) { y = yy; }
void zSet(double zz) { z = zz; }
void rSet(double rr) { r = rr; }
double xValue() { return x; }
double yValue() { return y; }
double zValue() { return z; }
double rValue() { return r; }
double volume() { return 4.0 / 3 * 3.1415926 * r * r * r; }
void display() { cout << "center point (" << x << "," << y << "," << z << ")" << '\t'
<< "radius = " << r << '\t' << "volume = " << this->volume() << endl; }
};
/*选择排序——元素p[a]~p[b]升序排序*/
void selesort(Ball p[], int a, int b)
{
int i, j, k;
Ball d; //用来交换的变量
for (i = a; i <= b - 1; i++) //对最后一个元素p[n-1]不用操作
{
k = i; //记录现在到哪里了
for (j = i + 1; j <= b; j++)
if (p[j].rValue() < p[k].rValue()) //比较的是半径
k = j; //从i的下一个开始找,如果有比i小元素(第j个)的就让k为j
//本质目的是找出i后面最小的一个
if (k != i) //如果i项不是最小的,那么换!
{
d = p[i]; //排序的是元素
p[i] = p[k];
p[k] = d;
}
}
}
int main()
{
Ball a[10];
Ball *b = new Ball[20];
double sumx = 0, sumy = 0, sumz = 0, sumr = 0, sumv = 0;
//a
sumx = sumy = sumz = sumr = sumv = 0;
for (int i = 0; i <= 10 - 1; i++)
{
a[i].xSet(rand() / 100.0);
sumx += a[i].xValue();
a[i].ySet(rand() / 100.0);
sumy += a[i].yValue();
a[i].zSet(rand() / 100.0);
sumz += a[i].zValue();
a[i].rSet(abs(rand()) / 10000.0);
sumr += a[i].rValue();
sumv += a[i].volume();
a[i].display();
}
selesort(a, 0, 9);
for (int i = 0; i <= 10 - 1; i++)
a[i].display();
cout << "AVERAGE:" << endl;
cout << "center point (" << sumx << "," << sumy << "," << sumz << ")" << '\t'
<< "radius = " << sumr << '\t' << "volume = " << sumv << endl;
//b
sumx = sumy = sumz = sumr = sumv = 0;
for (int i = 0; i <= 20 - 1; i++)
{
b[i].xSet(rand() / 100.0);
sumx += b[i].xValue();
b[i].ySet(rand() / 100.0);
sumy += b[i].yValue();
b[i].zSet(rand() / 100.0);
sumz += b[i].zValue();
b[i].rSet(abs(rand()) / 10000.0);
sumr += b[i].rValue();
sumv += b[i].volume();
b[i].display();
}
selesort(b, 0, 19);
for (int i = 0; i <= 20 - 1; i++)
b[i].display();
cout << "AVERAGE:" << endl;
cout << "center point (" << sumx << "," << sumy << "," << sumz << ")" << '\t'
<< "radius = " << sumr << '\t' << "volume = " << sumv << endl;
//END
delete[] b;
system("pause");
}