c++的类与对象
一:类以及对象的定义
类的定义:
定义一个类,本质上是定义一个数据类型的蓝图。
类定义是以关键字 class 开头,后跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表。
class Box
{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
double getVolume(void);// 返回体积
};
关键字 public 确定了类成员的访问属性。
在类对象作用域内,公共成员在类的外部是可访问的。您也可以指定类的成员为 private 或 protected。
对象的定义:
对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
二:访问数据成员
类的对象的公共数据成员可以使用直接成员**访问运算符 ** . 来访问。
私有的成员和受保护的成员不能使用直接成员访问运算符 (.) 来直接访问。
三:类成员函数
类的成员函数是指那些把定义和原型写在类定义内部的函数。需要注意的是:类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。
现在我们要使用成员函数来访问类的成员,而不是直接访问这些类的成员。
成员函数可以定义在类的内部,也可以定义在类的外部。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。(一定要搞清楚内联函数的定义)
内部定义:
class Box
{
public:
...
double getVolume(void)
{
return length * breadth * height;
}
};
外部定义(使用范围解析运算符 :: 定义该函数):
double Box::getVolume(void)
{
return length * breadth * height;
}
在这里,需要强调一点,在 :: 运算符之前必须使用类名。调用成员函数是在对象上使用点运算符(.)
四:访问修饰符(封装)
关键字 public、private、protected 称为访问修饰符,限制了对类成员的访问权限。
一个类可以有多个 public、protected 或 private 标记区域。
成员和类的默认访问修饰符是 private。
public成员
- 公有成员在程序中类的外部是可访问的。
private成员
- 私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。
- 只有类和友元函数可以访问私有成员。
- 我们一般会在私有区域定义数据,在公有区域定义相关的函数,以便在类的外部也可以调用这些函数。
protect成员
- protected(受保护)成员在派生类(即子类)中是可访问的。
- **protected(受保护)**成员变量或函数与私有成员十分相似。
五:构造和析构函数
构造函数:
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。
构造函数可用于为某些成员变量设置初始值。
不带参数的构造函数
class Line
{
private:
double length;
/* data */
public:
void setLength( double len );
double getLength( void );
Line();
};
Line::Line()
{
cout << "已经创建完成" << endl;
}
void Line::setLength(double len)
{
length = len;
}
double Line::getLength(void) {
return length;
}
int main() {
Line line;
line.setLength(2.3);
cout << "length of line:" << line.getLength() << endl;
return 0;
}
带参数的构造函数
- 这样在创建对象时就会给对象赋初始值。
class Line
{
private:
double length;
/* data */
public:
void setLength( double len );
double getLength( void );
Line(double len);
};
Line::Line(double len/* args */)
{
length = len;
cout << "已经创建完成" << endl;
}
void Line::setLength(double len)
{
length = len;
}
double Line::getLength(void) {
return length;
}
int main() {
//刚创建时候的初始化
Line line(102.33);
cout << "length of line:" << line.getLength() << endl;
//再次设置长度
line.setLength(2.3);
cout << "length of line:" << line.getLength() << endl;
return 0;
}
使用初始化列表来初始化字段
Line::Line( double len): length(len)
{
cout << "Object is being created, length = " << len << endl;
}
这种写法其实就等同于上面构造函数的写法。
如果有多个需要赋值的变量,可以使用以下格式进行赋值(在不同的字段之间使用逗号隔开):
C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
....
}
析构函数
定义:
是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。
析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。(这一点十分重要,假如手动开辟了一段内存空间,往往会忘记释放,这个时候析构函数的作用就体现出来了)
ps:感觉这个跳出程序就释放还挺有意思的。
class Line
{
private:
double length;
/* data */
public:
void setLength( double len );
double getLength( void );
Line(double len);
~Line();
};
Line::Line(double len/* args */)
{
length = len;
cout << "已经创建完成" << endl;
}
Line::~Line() {
cout << "已经删除完毕" << endl;
}
void Line::setLength(double len)
{
length = len;
}
double Line::getLength(void) {
return length;
}
int main() {
Line line(102.33);
cout << "length of line:" << line.getLength() << endl;
line.setLength(2.3);
cout << "length of line:" << line.getLength() << endl;
return 0;
}
六:拷贝构造函数
(在学习之前,我还挺疑惑这个拷贝和赋值有什么区别,结果搜索才发现是一个意思,只是叫法不同)
定义:
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。
如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。
拷贝构造函数通常使用的时机:
- 通过使用另一个同类型的对象来初始化新创建的对象。
- 对象以值传递的方式传入函数参数。
- 复制对象,并从函数返回这个对象。
class CExample
{
private:
int a;
public:
//构造函数
CExample(int b) {
a = b;
cout<<"creat: "<<a<<endl;
}
//拷贝构造
CExample(const CExample& C) {
a = C.a;
cout<<"copy"<<endl;
}
//析构函数
~CExample() {
cout<< "delete: "<<a<<endl;
}
void Show () {
cout<<a<<endl;
}
};
//全局函数,传入的是对象
void g_Fun(CExample C) {
cout<<"test"<<endl;
}
int main() {
CExample test(1);
//传入对象
g_Fun(test);
return 0;
}
调用g_Fun()时,会产生以下几个重要步骤:
(1)test对象传入形参时,会先会产生一个临时变量,就叫 C 吧。
(2)然后调用拷贝构造函数把test的值给C。 整个这两个步骤有点像:CExample C(test);
(3)等g_Fun()执行完后, 析构掉 C 对象。
(4)等程序执行完之后,也会把对象 test 析构掉。
Q:为什么当类成员中含有指针类型成员且需要对其分配内存时,一定要有总定义拷贝构造函数??
A:
默认的拷贝构造函数实现的只能是浅拷贝,即直接将原对象的数据成员值依次复制给新对象中对应的数据成员,并没有为新对象另外分配内存资源。
这样,如果对象的数据成员是指针,两个指针对象实际上指向的是同一块内存空间。
在某些情况下,浅拷贝回带来数据安全方面的隐患。
当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。
浅拷贝与深拷贝的区别:
浅拷贝:默认拷贝构造函数可以完成对象的数据成员的复制。
深拷贝:自己手写的构造函数完成对象的数据成员的复制。
如果碰到指针,浅拷贝会把指针变量的地址复制; 深拷贝会重新开辟内存空间。
深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。
//指针悬挂问题
class Test
{
private:
int* p;
public:
Test(int x)
{
this->p=new int(x);
cout << "对象被创建" << endl;
}
~Test()
{
if (p != NULL)
{
delete p;
}
cout << "对象被释放" << endl;
}
int getX() { return *p; }
};
int main()
{
Test a(10);
//会调用默认的拷贝构造函数
Test b = a;
return 0;
}
//解决指针悬挂问题
class Test
{
private:
int* p;
public:
Test(int x)
{
this->p=new int(x);
cout << "对象被创建" << endl;
}
~Test()
{
if (p != NULL)
{
delete p;
}
cout << "对象被释放" << endl;
}
int getX() { return *p; }
//深拷贝(拷贝构造函数)
Test(const Test& a)
{
this->p = new int(*a.p);
cout << "对象被创建" << endl;
}
};
int main()
{
Test a(10);
//我们手动的写拷贝构造函数,C++编译器会调用我们手动写的
Test b = a;
return 0;
}
七:友元函数
(目的其实是为了访问私有成员和保护成员)
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。
注意:
- 友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
- 友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend。
class Box
{
double width;
public:
double length;
friend void printWidth( Box box );
void setWidth( double wid );
};
class Box
{
double width;
public:
friend void printWidth( Box box );
void setWidth( double wid );
};
void Box::setWidth( double wid )
{
width = wid;
}
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}
int main( )
{
Box box;
// 使用成员函数设置宽度
box.setWidth(10.0);
// 使用友元函数输出宽度
printWidth( box );
return 0;
}
如果要声明类1为类2的友元,需要在类2的定义中放置如下声明:
friend class ClassTwo;
class Box
{
double width;
public:
friend void printWidth(Box box);
friend class BigBox;
void setWidth(double wid);
};
class BigBox
{
public :
void Print(int width, Box &box)
{
// BigBox是Box的友元类,它可以直接访问Box类的任何成员
box.setWidth(width);
cout << "Width of box : " << box.width << endl;
}
};
void Box::setWidth(double wid)
{
width = wid;
}
void printWidth(Box box)
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width << endl;
}
int main()
{
Box box;
BigBox big;
// 使用成员函数设置宽度
box.setWidth(10.0);
// 使用友元函数输出宽度
printWidth(box);
// 使用友元类中的方法设置宽度
big.Print(20, box);
//getchar();
return 0;
}
八:内联函数
定义:当函数被声明为内联函数之后, 编译器会将其内联展开, 而不是按通常的函数调用机制进行调用。
如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。类似于宏定义。
如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline。
Q:为什么要引入内联函数?
A:引入内联函数的目的是为了解决程序中函数调用的效率问题。编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。这其实就是个空间代价换时间的i节省。
九:this指针
(目的其实是为了访问自己本身设计的,this 指针的类型可理解为 Box*)
每一个对象都能通过 this 指针来访问自己的地址。
this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
class Box
{
public:
Box(double l=2.0, double b=2.0, double h=2.0)
{
length = l;
breadth = b;
height = h;
}
double Volume()
{
cout << length * breadth * height << endl;
return length * breadth * height;
}
int compare(Box box)
{
return this->Volume() > box.Volume();
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
int main()
{
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
if(Box1.compare(Box2)) {
cout << "Box2 is smaller than Box1" <<endl;
} else {
cout << "Box2 is equal to or larger than Box1" <<endl;
}
return 0;
}
十:指向类的指针
一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 ->,就像访问指向结构的指针一样。与所有的指针一样,您必须在使用指针之前,对指针进行初始化。
十一:静态成员&静态成员函数
(第一反应其实是静态变量,也不知道到底有什么具体区别)
当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
静态成员在类的所有对象中是共享的。
我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化。
class Box
{
public:
static int objectCount; //静态成员
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
private:
double length;
double breadth;
double height;
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
int main()
{
Box Box1(3.3, 1.2, 1.5);
Box Box2(8.5, 6.0, 2.0);
// 输出对象的总数
cout << "Total objects: " << Box::objectCount << endl;
return 0;
}
静态成员函数
把函数与类的任何特定对象独立开来。
静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
注意:
-
静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
-
静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。
class Box
{
public:
static int objectCount; //静态成员
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
static int getCount() //静态成员函数
{
return objectCount;
}
private:
double length;
double breadth;
double height;
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
int main()
{
// 在创建对象之前输出对象的总数
cout << "Inital Stage Count: " << Box::getCount() << endl;
Box Box1(3.3, 1.2, 1.5);
Box Box2(8.5, 6.0, 2.0);
// 在创建对象之后输出对象的总数
cout << "Final Stage Count: " << Box::getCount() << endl;
return 0;
}