类的三大属性:
private,public,protected
1,对于类的成员变量或者函数,缺省即为私有
#include <iostream> using namespace std; class A { int y; //私有成员 int x; //私有成员 public: A(int xx, int yy) {x = xx; y = yy;} void setx(int m) {x = m;} void sety(int n) {y = n;} }; int main() { A a(1,2); }
2,对于类的继承而言,缺省也是私有继承!!!!!
#include <iostream> using namespace std; class A { int y; int x; public: A(int xx, int yy) {x = xx; y = yy;} void setx(int m) {x = m;} void sety(int n) {y = n;} }; class B:A { int x; public: B(int xx, int yy,int xxx):A(xx,yy) { x = xxx; } }; int main() { B b(1,2,3); }
如上,B也是同样的私有继承A了。。。。
3,内联函数
内联函数是一种特殊的函数,本身比较少,运行起来也特别快,,由于函数调用会产生一定的调用语句,参数的
传递,入栈出栈,那么当这个函数被反复调用的时候,就会产生很大的开销。于是就产生了内联函数。
当我们的编译器看到了关键字:inline的时候,那么编译器会把函数的实现全部插入到调用内联函数出,这样就
不会产生额外的调用开销了
内联成员函数:
1,inline + 成员函数
2,整个函数体出现在定义类的内部
#include <iostream> using namespace std; class A { int y; int x; public: A(int xx, int yy) {x = xx; y = yy;} void setx(int m) {x = m;} //为内联函数,函数的定义在class中 inline void sety(int n); //同样的为内联函数,通过inline声明 }; void A::sety(int n) { y = n; } int main() { A a(1,2); }
内联函数,从代码层看,有函数的结构,而编译后却没有函数的性质,不是在调用时候发生控制转移,而是
在编译的时候,将函数体嵌入到每一个调用之处。
3,成员函数的重载以及参数缺省
我们知道函数是可以重载的,不同的返回值类型或者函数的个数是不相同的,那么就是函数的重载,同样:成员
函数也是可以重载的。成员函数也是可以带有缺省的参数的。C++语法规定,函数在写的时候可以有缺省值,那么
在调用的时候,如果我们没有给参数,那么参数就是缺省值。
#include <iostream> using namespace std; class A { int y; int x; public: void init(int x = 0, int y = 0); //缺省参数 void setx(int m) {x = m;} //下面这两个函数为setx函数的重载 int setx() {return x;} }; int main() { A a(1,2); }
这里我们需要注意的就是使用:缺省参数时和函数重载时的二异行,,,,,
如下:程序
#include <iostream> using namespace std; class A { int y; int x; public: void init(int xx = 0, int yy = 0); //缺省参数 void setx(int m =0) {x = m;} int setx() {return x;} }; int main() { A a(1,2); a.setx(); }
如上的setx函数,a调用setx函数,就不知道调用的是哪一个,就会产生编译出错,,,,:
5,构造函数
一:成员函数的一种,名字与类名相同,可以有参数,不能有返回值(任何类型都不行,包括void)
二:作用是对类对象进行初始化,如:给成员变量赋初值
三:如果定义类的时候没有写构造函数,则编译器生成一个默认的无参构造函数(不做任何操作)
四:如果我们定义了构造函数,那么编译器不会生成默认的无参构造函数
五:对象生成时构造函数自动被调用(一定),对象一旦生成,就再也不能在其上执行构造函数
六:一个类中可以有多个构造函数,构造函数的重载,那么如果要调用那个构造函数,需要看我们的对象是如何
调用的
那么为什么需要构造函数呢????
构造函数执行必要的初始化工作,有了构造函数就不必在专门写必要的初始化函数了,也不用担心忘记了写
初始化函数了,有时候对象没有被初始化就使用,那么就会导致编译错误了。。。。
#include <iostream> using namespace std; class A { int y; int x; public: void init(int xx = 0, int yy = 0); //缺省参数 void setx(int m =0) {x = m;} }; //编译器自动的调用默认构造函数 int main() { A a; //默认的构造函数被调用 A *pc = new A; //默认的构造函数被调用 pc->init(1,2); }
如上,只要有对象生成,就一定会调用构造函数
构造函数的重载及其调用问题:
#include <iostream> using namespace std; class A { double y; double x; public: A(double xx, double yy) { x = xx; y = yy;} A(double xxx) { x = xxx;} void setx(int m =0) {x = m;} }; //构造函数的重载 int main() { A a(2,3); //调用的是第一个构造函数,整形被转化成了double类型 A b(2); //调用的是第二个构造函数,同样的被转化成了double }
构造函数在数组(对象数组)中的使用:
#include <iostream> using namespace std; class A { double x; public: A() { cout<<"Construction 1"<<endl; } A(double xx) { cout<<"Construction 2"<<endl; x = xx;} void setx(int m =0) {x = m;} }; int main() { A a1[2]; //调用无参的构造函数 cout<<"step1"<<endl; A a2[2] = {4,5}; //调用有参的构造函数初始化 cout<<"step2"<<endl; A a3[2] = {3}; //一个调用有参的构造函数,一个调用无参的构造函数 A *p = new A[2]; //调用无参的构造函数 delete[] p; }
运行结果:
如上:这里还是有很多不同的,,,,,,生成一个对象就一定要调用构造函数
还有一种特殊的例子,我们来看一下:
#include <iostream> using namespace std; class A { double x; double y; public: A() //无参构造函数 { cout<<"Construction 1"<<endl; } A(double xxx) //一个参数构造函数 { cout<<"Construction 2"<<endl; x = xxx;} A(double xx, double yy) //两个参数构造函数 { cout<<"Construction 3"<<endl; x = xx; y = yy;} void setx(int m =0) {x = m;} }; int main() { A a[3] = {1, A(2,3)}; //定义了一个对象数组,第一个一个元素初始化,第二个两个元素,第三个没有初始化 A *p[3] = {new A(3), new A, new A(4,5)}; }
运行结果:
从上面可以看出,我们的对象数组是这个样子进行初始化的。。。。。。。。。。。并且也是这个样子调用new的。
其实总的来说:
对于对象数组的初始化:嗯嗯,好像只能用这种方式:
A a[3] = {A(1), A(2,3)}; A *p[3] = {new A(3), new A, new A(4,5)};
嗯嗯,就是,这种方式:上面我们实现的方式是不一样的,也就是说:当只有一个参数的时候
A(1)和1是一样的,,,,,,,,,,,
即就是:
A a = {1},A a(1),A a = {A(1)}; //三者是一样的!!!!!
6,复制构造函数(Copy Constructor)
我们经常使用函数,传递各种各样的参数,而我们今天要说的复制构造函数:就是把对象(注意是对象,不是
对象的指针或者对象的引用)作为一个参数,并且传递给函数
我们也知道,把参数传递给函数有三种方法:值传递,地址传递,传引用。
值传递时,传送的是作为实际参数的对象的副本而不是实际对象本身。而且这个副本的内容是按位从原始参数
那里复制过来的,内容是相同的。
当传递过来的时候,如果是类的对象呢???会不会触发类的构造函数呢,,调用结束,会不会触发析构呢???
从程序看:
#include <iostream> using namespace std; class A { int x; public: A(int x1) { x = x1; cout<<"Constructor"<<endl; } //构造函数的实现 ~A() { cout<<"Destructor"<<endl; } //析构函数的实现 void print() { cout<<x<<endl; } }; void f(A a) { a.print(); } int main() { A a(10); f(a); a.print(); return 0; }
运行结果:
从运行结果可以看到:A类的构造函数只被调用了一次,发生在创建对象a的时候,而A类的析构函数却被调用了两次
这是为什么呢???
查阅书籍后发现:如同我们上面所提到的,把一个对象作为参数传递给函数时,同时创建了该对象的副本(这
个副本将作为函数的参数)。也就是说:创建了一个新的对象。当函数结束时,作为函数的实际参数的副本将被
销毁,这产生了两个有趣的问题。。。。。。。
1,在创建对象的副本时有没有调用构造函数
2,在销毁对象的副本时有没有调用了析构函数
首先,在调用函数时,程序创建了一个对象的副本作为形式参数,此时普通的构造函数并没有被调用,而是调用
了复制构造函数,复制构造函数定义了如何创建了一个对象的副本。。。。。如果一个类中没有显示的地定义类
的复制构造函数,那么C++将提供一个默认的复制构造函数。默认的复制构造函数将以按位复制的形式创建一个
对象的副本,即创建一个与原对象一样的副本。
由于普通的构造函数通常用于初始化对象的某些成员,因此就不能调用普通的构造函数创建对象的副本,因为
这样产生的对象可能与现有的对象的状态属性不完全相同。很简单,只是因为当把一个对象传递给函数时,需要
使用的是对象的当前状态,而不是初始状态。。。。
其次,当函数结束时,由于作为参数的对象副本超出了作用域,因此它将被销毁,从而调用了析构函数,这就是
为什么前面的程序中调用了两次析构函数的原因,第一次函数f()参数超出了作用域,第二次是因为main函数
中的对象a在程序结束时被销毁。。。。
综上所述:当创建一个对象的副本作为函数参数时,普通的构造函数没有被调用,所调用的构造函数是按位复制
的默认复制构造函数。但是,当对象的副本被销毁时(通常因为,函数返回而超出了作用域),析构函数被调用
,如果析构函数是空的话,通常不会发生什么问题,但是一般情况下:析构函数都要完成一些清理工作(释放
内存。。。。)
假如,我们真的在复制构造函数里面为一个指针变量分配了内存,在析构函数里释放了分配给这个指针所指向的
空间,那么我们可以想想,在把对象传递给函数到整个main函数返回的时候,发生了什么???
首先,有一个对象的副本产生了,这个副本也有一个指针,,,它和原始对象的指针指向同块内存空间,,函数
返回时,副本对象的析构函数被执行了,,,释放了对象副本里指针所执行的内存空间,但是这个内存空间对于
原始对象来说,还是有用的啊,,,,(干嘛就直接给释放了呢???),就程序本身而言,这是一个错误,
但是错误还是没有停止,,,等到main函数结束的时候,原始对象的析构函数继续被执行,,,对同一块内存
释放两次,这有可能加深错误!!!
那么,如何解决上面的问题呢???
嗯嗯,可以用传地址,传引用,这样却是可以合理的解决这样一个问题,但是,有的时候,我们不需要直接的改
变外部变量啊,,,那如何处理呢????
其实,这个时候,我们可以用复制构造函数来解决这个问题,复制构造函数是在产生对象副本的时候执行的,
我们可以调用自己的复制构造函数。在自己的复制构造函数里,我们可以申请一个新的内存空间来保存那个
构造函数所指向的内容。。。。这样,在执行对象副本的时候,执行的就是刚刚申请的那个空间了。。。
、
复制构造函数(拷贝构造函数)
X& const X& volatile X& const volatile X& //后面也可以跟其他的参数。。。。。。。。。。。。。
如上,就是复制构造函数的几种类型。。。。。。。。。
1, 当一个对象副本被作为参数传递给函数时
display(y);
2, 当一个对象被另一个对象显式的初始化时
my_string x = y;
3, 当创建一个临时对象时(最常见的是:作为函数的返回值)
y = func2();
前面2中情况,对象y的引用将被传递给复制构造函数;在第三种情况下,函数func2()返回的对象引用将被传递给
y的复制构造函数。。。。
关于对象之间的显示初始化。。。
#include <iostream> using namespace std; class A { int x; public: A(int x1) { x = x1; cout<<"Constructor"<<endl; } //构造函数的实现 ~A() { cout<<"Destructor"<<endl; } //析构函数的实现 void print() { cout<<x<<endl; } }; int main() { A a(10); A b = a; // 可以这样初始化 A c(a); // 也可以这样初始化 return 0; }
运行结果:
其实后面两个的初始化调用的是复制构造函数,只是在当前类中我们没写,调用的是默认,析构调用了 ,,,,
程序:
#include <iostream> using namespace std; class A { int x; public: A(int x1) { x = x1; cout<<"Constructor"<<endl; } //构造函数的实现 A(A & a) //自定义的复制构造函数 { x = a.x; cout<<"自定义的 Constructor"<<endl;} ~A() { cout<<"Destructor"<<endl; } //析构函数的实现 void print() { cout<<x<<endl; } }; int main() { A a(10); A b = a; A c(a); return 0; }
程序如上:我们写了自定义的复制构造函数
运行结果:
为了明显的区分开跟赋值的区别,我们重载了一个赋值运算符:
#include <iostream> using namespace std; class A { int x; public: A(int x1) { x = x1; cout<<"Constructor"<<endl; } //构造函数的实现 A(A & a) //自定义的复制构造函数 { x = a.x; cout<<"自定义的 Constructor"<<endl;} A &operator = (A & a) //重载了一个赋值运算符 { this->x = a.x; cout<<"赋值运算符的重载"<<endl;} ~A() { cout<<"Destructor"<<endl; } //析构函数的实现 void print() { cout<<x<<endl; } }; int main() { A a(10); A b(2); b = a; return 0; }
运行结果:
1,上面我们知道了一些关于复制构造函数里面的一些东西。。。。那么我们有一些疑惑???
复制构造函数可以重载吗????
可以的,因为上面我们提到了,复制构造函数的类型;;;;;;;;
#include <iostream> using namespace std; class A { int x; public: A(int x1) { x = x1; cout<<"Constructor"<<endl; } //构造函数的实现 A(A & a) //自定义的复制构造函数 { x = a.x; cout<<"自定义的 Constructor"<<endl;} A(A & a, int b) { a.x = b; cout<<"重载的复制构造函数"<<endl; } ~A() { cout<<"Destructor"<<endl; } //析构函数的实现 void print() { cout<<x<<endl; } }; int main() { A a(2); A b(a, 3); return 0; }
如上:我们重载了复制构造函数....
运行结果:
2,拷贝构造函数中可以调用private成员变量吗???
从上面的程序中可以看到,这里的调用无压力,,,,可以直接调用,,,因为是一种特殊的构造函数
操作的是自己的成员变量,不用考虑private。。。。。
关于深拷贝,浅拷贝
其实说白了:编译器默认的拷贝构造函数其实就是一个浅拷贝,如同我们上面所提到的那样,如果有指针变量的
话,那么就会出错,因为拷贝后两个指针指向了同一块空间,释放两次自然就会出错。。。。而深拷贝,不但对指针
进行拷贝,也对指向的内容进行拷贝。。。。。。。拷贝后,是两个指向不同地址的指针。。。。。。
1,那么浅拷贝会出现什么问题呢????
int main() { A a(2); A b = a; // A c(a); 上面这两种情况都是可以的 return 0; }
假设类有一个成员变量指针:char *p;
1,浅拷贝只是拷贝了指针,使得两个指针指向同一个地址,这样在对象块结束,调用函数析构的时,会造成同一
份资源析构2次,即delete同一块内存2次,造成程序崩溃。
2,浅拷贝使得b.p和a.p指向同一块内存,任何一方的变动都会影响到另一方
3,结果是会调用两次析构函数,造成同一块内存释放两次,明显的错误
如下:
#include <string.h> #include <iostream> using namespace std; class A { char *p; public: A(char *s) //构造函数 { cout<<"Constructor"<<endl; p = new char[strlen(s) + 1]; strcpy(p, s); } ~A() //析构函数 { if(p != NULL) { delete p; p = NULL; cout<<"析构函数"<<endl; } } }; int main() { char p[] = "abc"; A a(p); return 0; }
运行结果:
一块空间的多次释放,自然而然的造成了错误。 ,,,,,,,,,,,,,,
深拷贝采用了在堆内存中申请新的空间来存储数据,在我们重新书写的复制构造函数里我们重新为这个新的指针
分配了一块存储空间了,,,,,,,,,,
#include <string.h> #include <iostream> using namespace std; class A { char *p; public: A(char *s) //构造函数 { cout<<"Constructor"<<endl; p = new char[strlen(s) + 1]; strcpy(p, s); } A(A & a) { cout<<"复制Constructor"<<endl; int length = strlen(a.p); p = new char[length + 1]; strcpy(p, a.p); } ~A() //析构函数 { if(p != NULL) { delete p; p = NULL; cout<<"析构函数"<<endl; } } }; int main() { char p[] = "abc"; A a(p); A b = a; return 0; }
可以看到:我们重新写了复制构造函数,并且在其中重新分配了内存空间了。。。。
运行结果: