C++中NULL不能写作小写,NULL的值为零,也可以写作0
在自己写的复制构造函数中不改变原对象,所以传进来的参数可以设为const类型的,这样可以保证传进来的对象不被改变
比如A(const A &a)
同时注意,如果再函数体中不改变任何变量,那么这个函数也可以设为const,也就是把函数体前(大括号前面)面加上const
编译器提供默认构造函数和析构函数,同时也提供复制构造函数,编译器提供的默认复制构造函数只是把传递进来的对象的每个成员变量复制到新对象的成员变量中去,然后两个对象中的变量均指向存放类成员变量的那块内存区域,假如该变量不是指针变量一般不会出错,假如该变量是指针变量,释放一个对象的内存空间的时候另一个对象会受到影响,因为本来两个对象中的指针指向同一块内存区域,释放其中一个对象的时候,其中的指针变量指向的内存区域也被释放,这时候没被释放的对象中的指针变量成员就成为一个迷途指针,找不到原来指向的那个对象的变量了(这时候需要自己写深层复制构造函数函数,代码在下面)
下面用代码演示默认复制构造函数:
#include <iostream>
using namespace std;
class A
{
public:
A(){x = new int; *x = ;}
~A() {delete x; x = NULL;}
A(const A &a)
{
cout << "复制构造函数执行!\n" << endl;
x = a.x; //其中x是新复制的对象的成员,可以用this.x表示,不加this的时候,系统默认加上
}//我们不写复制构造函数的时候系统自动为我们加上这样一个复制构造函数
void print()const{cout << *x << endl;}
void set(int i){*x = i;}
private:
int *x;
};
int main()
{
A *a = new A;
a->print();
A b = (*a);
b.print();
a->set();
b.print();
delete a;
b.print();
return ;
}
假设自己写构造函数就不会出现浅层复制造成的错误:(上述代码改为深层复制后代码如下)
#include <iostream>
using namespace std;
class A
{
public:
A(){x = new int; *x = ;}
~A() {delete x; x = NULL;}
A(const A &a)
{
cout << "复制构造函数执行!\n" << endl;
x = new int;
*x = *(a.x);
}
int get()const{return *x;}
void set(int i){*x = i;}
private:
int *x;
};
int main()
{
A *a = new A;
cout << "*(a.x):" << a->get() << endl;
A b = (*a);
cout << "*(b.x):" << b.get() << endl;
a->set();
cout << "*(a.x):" << a->get() << endl;
cout << "*(b.x):" << b.get() << endl;
delete a;
cout << "删除a之后*(b.x):" << b.get() << endl;
return ;
}
复制构造函数会完成两个对象之间的拷贝
隐式转换有时候会造成一些错误,需要关闭隐式转换的话可以在构造对象的时候加上关键词explicit,不加explicit的话默认可以进行隐式转换
我们可以把数字当作对象赋给另一个对象,这样在对该赋值表达式进行计算的时候,首先要对数字进行类型转换,同时判断该类的构造函数的参数是否与数字的类型匹配,假如匹配则调用 构造函数创建一个临时对象,跟着将该临时对象赋给复制操作左边的对象,最后调用析构函数删除临时对象(判断匹配然后转换的过程叫做隐式转换)
#include <iostream>
using namespace std;
class A
{
public:
explicit A(int x){i = x; cout << "构造函数执行!" << endl;}
~A() {cout << "析构函数执行!\n";}
void get() {cout << i << endl;}
private:
int i;
};
int main()
{
A a();
a.get();
a = A(); //这里也可以写成a = 1000;这样的话该对象的构造函数前面不能加explicit,
return ; //因为加上explicit之后,该对象就不能进行隐式类型转换了
}
delete会调用析构函数删除在堆中创建的空间
#include <iostream>
using namespace std;
//本代码演示了delete 会调用析构函数来释放new运算符创建的内存空间
//本代码还演示了在堆中创建对象
//注意在堆中创建的对象必须手动删除,本代码没有删除第二个对象
class A
{
public:
A(){cout << "构造函数执行!" << endl;}
~A(){cout << "析构函数执行!\n";}
void set(int a, char b)
{
i = a;
j = b;
}
void print()
{
cout << i << " " << j << endl;
}
private:
int i;
char j;
};
int main()
{
A *a;
a = new A; //在堆中创建对象
a->set(, 'a');
a->print();
delete a;
cout << endl << endl;
A *b = new A;
b->set(, );
return ;
}
复制构造函数:(注:如果不写复制构造函数的话系统自动加上默认复制构造函数)
#include <iostream>
using namespace std;
class A
{
public:
A(){}
A(A&one){n = one.n; m = one.m;} //默认构造函数就是这样写的,假设我们不写,系统自动加上
A(int i, int j){n = i; m = j;}
void print()
{
cout << "n: " << n << " m: " << m << endl;
}
private:
int n, m;
};
int main()
{
A a(, );
a.print();
A b(a); //这时候a的值就传给b了
b.print();
return ;
}
函数声明的时候可以只写参数类型,不写参数名
当函数的参数缺省的时候,如果给函数传递参数,则函数的参数为传进来的数,否则
函数的参数为默认值
函数参数缺省和函数重载的使用有几分相像,但是不同:
具有默认参数的函数重载的是参数的数值,而重载函数重载的是参数的类型。
1:普通函数参数的缺省
#include <iostream>
using namespace std;
void fun(int n = , int m = )
{
cout << "n:" << n << " m:" << m << endl;
} int main()
{
fun(); //输出默认参数0 0
fun(,);//输出参数1, 2
return ;
}
2:类中方法的参数缺省
#include <iostream>
using namespace std;
class A
{
public:
void set(int = , int = );
void count(bool = false);
private:
int w;
int h;
};
void A::set(int width, int height)
{
w = width;
h = height;
}
void A::count(bool val)
{
if(val)
cout << "val的值为真时:" << w*h << endl;
else
cout << "val的值为假时:" << w*h/ << endl;
}
int main()
{
A a;
a.set();
a.count();
a.count(true);
a.set(,);
a.count();
a.count(true);
return ;
}
不仅普通函数可以重载,构造函数也可以重载:
/*构造函数也可以重载*/
#include <iostream>
using namespace std;
class rectangle
{
public:
rectangle(){cout << "构建一个长方形\n";}
rectangle(int l, int w){ length = l; width = w; cout << "长方形的面积为:" << length*width << endl;}
rectangle(int l, int w, int h){length = l; width = w; height = h; cout << "长方体的体积为:" << l*w*h << endl;}
private:
int length;
int width;
int height;
};
int main()
{
rectangle();
rectangle(, );
rectangle(, , );
return ;
}
对成员变量初始化的另一种方式
在函数体前面也就是函数名后面加冒号同时加上变量和对应的初始化值,
这种方法是对成员变量的初始化,而不是赋值
/*可以在构造函数头对常量和引用初始化;这种方法和在函数中对变量赋值都是给成员变量一个值,但是有所不同
1:在函数头实际上是初始化
2:在函数中的语句实际是赋值语句
区分赋值语句和初始化语句
初始化:即定义的时候进行赋值 比如说你定义的某个对象的成员是常量,那么在该对象的方法体中对该常量赋值会报错
但是在函数头对该常量赋值就会正常运行
由此我们可以看出在函数头和在函数中给一个变量赋值的区别
在函数头的实际是初始化语句,在函数中对对象的成员赋值才是赋值语句*/ #include <iostream>
using namespace std;
class rectangle
{
public:
rectangle():length(), width(){cout << "长方形的面积为:"<< length * width;}
private: //在函数名字后面加上冒号,然后是给每个变量赋的值,如果有多个赋值语句,中间用逗号隔开
const int length; //因为在函数名字后面加冒号写上给他们的值得话相当于对成员变量初始化,所以不会报错
int width; //如果直接在函数体中对常量赋值会报错,因为不能对常量赋值,只能对常量初始化
};
int main()
{
rectangle();
return ;
}
构造函数的初始化顺序:
它是按照定义时候的成员列表中的初始化顺序来的,析构的时候是先析构最后构造的那个成员
#include <iostream>
using namespace std;
/*构造函数的初始化顺序,它是按照成员列表的初始化顺序来的,并不看你在构造函数中先对谁赋值 然而析构函数是首先析构成员列表中最后构造的函数*/
class demo
{
public:
demo(){x = ; cout << "demo的默认构造函数" << endl;}
demo(int i){x = i; cout << "demo的带一个参数的构造函数" << x << endl;}
~demo(){cout << "demo的默认析构函数" << endl;}
int get(){return x;}
void set(int i) {x = i;}
private:
int x;
};
class rectangle
{
public:
rectangle(){x = ; cout << "rectangle的默认构造函数" << endl;};
rectangle(int i, int j, int k):x(i), width(j), length(k)
{
cout << "rectangle的带三个参数的构造函数!" << "长方形的面积为:" << length.get()*width.get() << endl;
}
~rectangle(){cout << "rectangle的默认析构函数" << x << endl;}
private:
int x;
demo width;
demo length;
};
int main()
{
rectangle a(, , );
return ;
}
再强调下常量和引用必须初始化,如果是类中的对象的话,需要按照在函数体前面赋值的方法初始化
#include <iostream>
class A
{
public:
A(int a, int b):num(a),total(b)
{
/*
下面的写法不对,因为常量和引用只能初始化不能赋值
num = a;
total = b;
*/
}
private:
const int num;
int &total;
};
int main()
{
A a(, );
return ;
}