1.类成员函数
class Box { public: double length; // 长度 double breadth; // 宽度 double height; // 高度 double getVolume(void);// 返回体积 };
//类的成员函数 double Box::getVolume(void) { return length * breadth * height; }
成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。
函数如果直接在类中定义的话,则该函数默认为内联的(不太确定)。
2.类访问修饰符
public: // 公有成员 公有成员在程序中类的外部是可访问的。您可以不使用任何成员函数来设置和获取公有变量的值 protected: // 受保护成员 保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的。 private:// 私有成员 私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。 默认情况下,类的所有成员都是私有的
如果继承时不显示声明是 private,protected,public 继承,则默认是 private 继承,在 struct 中默认 public 继承:
3.构造函数&析构函数
构造函数(有参和无参):
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
使用初始化列表来初始化字段
Line::Line( double len): length(len) { cout << "Object is being created, length = " << len << endl; } //上面的语法等同于如下语法:
Line::Line( double len) { length = len; cout << "Object is being created, length = " << len << endl; }
//假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,
C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
....
}
析构函数:
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
4.c++拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
-
通过使用另一个同类型的对象来初始化新创建的对象。
-
复制对象把它作为参数传递给函数。
-
复制对象,并从函数返回这个对象。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。
//拷贝构造函数的最常见形式 classname (const classname &obj) { // 构造函数的主体 }
#include <iostream> using namespace std; class Line { public: int getLength( void ); Line( int len ); // 简单的构造函数 Line( const Line &obj); // 拷贝构造函数 ~Line(); // 析构函数 private: int *ptr; }; // 成员函数定义,包括构造函数 Line::Line(int len) { cout << "调用构造函数" << endl; // 为指针分配内存 ptr = new int; *ptr = len; } Line::Line(const Line &obj) { cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl; ptr = new int; *ptr = *obj.ptr; // 拷贝值 } Line::~Line(void) { cout << "释放内存" << endl; delete ptr; } int Line::getLength( void ) { return *ptr; } void display(Line obj) { cout << "line 大小 : " << obj.getLength() <<endl; } // 程序的主函数 int main( ) { Line line1(10); Line line2 = line1; // 调用了line2的拷贝构造函数,line2还没有构造
//Line line3(20);
//line3=line1; //这个地方才调用重载运算符!!!line3已经构造初始化过了
display(line1); //对象作为参数传递给函数也会调用拷贝构造函数,不再调用构造函数 display(line2); return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
调用构造函数 //line1的构造函数 调用拷贝构造函数并为指针 ptr 分配内存 //line2的拷贝构造函数 调用拷贝构造函数并为指针 ptr 分配内存 //display(line1)先调用拷贝构造,深拷贝一份line1 line 大小 : 10 //display(line1)内容打印大小 释放内存 //display(line1)函数执行完,局部深拷贝拷贝出来的对象执行析构 调用拷贝构造函数并为指针 ptr 分配内存 //同理display(line2) line 大小 : 10 释放内存 释放内存 //最后两个释放为line2和line2执行的析构函数 释放内存
总结:拷贝构造其实也是构造函数,在调用拷贝构造的三种情况下,无非就是通过深拷贝复制数据内容,但是新对象的地址是变的(记住拷贝构造也是构造函数)。可能有的小伙伴有误解,拷贝构造不是实现用的引用吗,新对象的地址应该没有变化啊。其实这个里面有一个误区,就是拷贝构造的引用其实是为了深拷贝原对象数据内容,这里的引用可以简单的认为就是使用原对象,而新对象其实已经开辟空间地址了(细探类开辟空间)。不信你看看构造函数,它也没有专门给开辟首地址。
拷贝构造函数和赋值的区别
- 用一个已存在的对象去构造一个不存在的对象(构造之前不存在),就是拷贝构造.
- 用一个已存在的对象去覆盖另一个已存在的对象,就是赋值运算.
- 拷贝构造函数从一个已经存在的变量来初始化一个新声明的变量,不需要清除现有的值(因为是新创建,所以没有现有值)
- 拷贝构造函数没有返回值。
- 赋值运算符return *this.(This is necessary to allow multiple assignment, eg x = y = z;)
5.c++友元函数
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend,如下所示:
class Box { double width; public: double length; friend void printWidth( Box box ); //在类中声明了,但是并不是类的成员函数 void setWidth( double wid ); };
声明类 ClassTwo 的所有成员函数作为类 ClassOne 的友元,需要在类 ClassOne 的定义中放置如下声明:
friend class ClassTwo;
使用友元函数注意的要点:
- 类中通过使用关键字friend 来修饰友元函数,但该函数并不是类的成员函数,其声明可以放在类的私有部分,也可放在共有部分。友元函数的定义在类体外实现,不需要加类限定。
- 一个类中的成员函数可以是另外一个类的友元函数,而且一个函数可以是多个类友元函数。
- 友元函数可以访问类中的私有成员和其他数据,但是访问不可直接使用数据成员,需要通过对对象进行引用。
- 友元函数在调用上同一般函数一样,不必通过对对象进行引用。
例子:
#include <iostream> using namespace std; 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; } // 请注意:printWidth() 不是任何类的成员函数 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; }
6.c++内联函数(提升执行效率)
函数调用是有时间和空间开销的。程序在执行一个函数之前需要做一些准备工作,要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。
为了消除函数调用的时空开销,C++ 提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联函数(Inline Function),又称内嵌函数或者内置函数。指定内联函数的方法很简单,只需要在函数定义处增加 inline 关键字。
#include <iostream> using namespace std; //内联函数,交换两个数的值 //注意,要在函数定义处添加 inline 关键字,在函数声明处添加 inline 关键字虽然没有错,但这种做法是无效的,编译器会忽略函数声明处的 inline 关键字。 inline void swap(int a, int b) { int temp; temp = a; a = b; b = temp; cout<<a<<", "<<b<<endl; } int main() { swap(1,2); swap(2,3); swap(3,4); return 0; }
对于上面的程序,每次遇到“swap()”函数时,都会被替换为:“int temp;temp = a;a = b;b = temp;cout<<a<<", "<<b<<endl;”,就像C语言的宏替换一样,这样做的好处是什么呢?
首先需要注意的是,上面写的“swap()”这个函数是在文件编译时就被替换了,也就是说最终的主函数里面实际上是很多条语句的,因为我调用了那么多次“swap()”函数,每个都被替换成上面绿色部分的那么多语句了,这样的好处就是,程序真正开始运行时,跑到这里时不用再去另外调用“swap()”函数,直接执行被替换的东西就行了,这样以来就省去了电脑在来回调用函数时的开销。
上面说的内联函数的优点里面,其实同时也说出来了内联函数的缺点,那就是:内联函数你每调用一次,都会被最终替换成那么多语句,实际上就是编译器又为你每次都再把那些语句多写了一次,这样以来,程序的体积就变大了。
所以,综上所述:一般只将那些短小的、频繁调用的函数声明为内联函数。如果一个函数频繁调用,但是这个函数很长,那就不适合声明为内联函数,要不然每替换一次,就又多出来那么多语句,程序体积就太大了。
7.c++中的this指针
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
几点注意:
- this 是 const 指针,它的值是不能被修改的,一切企图修改该指针的操作,如赋值、递增、递减等都是不允许的。
- this 只能在成员函数内部使用,用在其他地方没有意义,也是非法的。
- 只有当对象被创建后 this 才有意义,因此不能在 static 成员函数中使用(后续会讲到 static 成员)。
this到底是什么?
this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。
this 作为隐式形参,本质上是成员函数的局部变量,所以只能用在成员函数的内部,并且只有在通过对象调用成员函数时才给 this 赋值。
在《C++函数编译原理和成员函数的实现》一节中讲到,成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是 this,它是成员函数和成员变量关联的桥梁。
8.c++中指向类的指针
一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 ->,就像访问指向结构的指针一样。与所有的指针一样,您必须在使用指针之前,对指针进行初始化。
9.c++类的静态成员
我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化。
静态成员函数
如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。
静态成员函数与普通成员函数的区别:
- 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
- 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。
#include <iostream> using namespace std; 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; // 每次创建对象时增加 1 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(void) { // 在创建对象之前输出对象的总数 cout << "Inital Stage Count: " << Box::getCount() << endl; Box Box1(3.3, 1.2, 1.5); // 声明 box1 Box Box2(8.5, 6.0, 2.0); // 声明 box2 // 在创建对象之后输出对象的总数 cout << "Final Stage Count: " << Box::getCount() << endl; return 0; }
运行结果:
Inital Stage Count: 0 Constructor called. Constructor called. Final Stage Count: 2