一.构造函数
构造函数就是对类对象进行初始化赋值。构造函数由编译器自动调用,且整个过程只调用一次。
1.构造函数性质
- 没有返回值也不写void
- 可以有参数和函数重载。
- 构造函数函数名与类名一致。
- 构造函数由编译器自动调用,且整个过程只调用一次。
语法格式:
#include<iostream>
using namespace std;
class A{
public:
A()
{
cout<<"构造函数A()"<<endl;
}
A(int num)
{
cout<<"构造函数A(int num)"<<endl;
}
private:
};
int main()
{
A test;
A test1(4); //注意名称
}
2.构造函数重载分类及调用
构造函数分为有残构造和无参构造,如上代码。
按类型分为普通构造和拷贝构造。
(1)普通构造函数重载调用方法:(也可以类外定义,类内声明)
括号法、隐式法、显示法
#include<iostream>
using namespace std;
class A{
public:
A()
{
cout<<"构造函数A()"<<endl;
}
A(int num)
{
cout<<"构造函数A(int num)"<<endl;
}
A(const A & a)
{
cout<<"拷贝函数A(int num)"<<endl;
}
private:
};
int main()
{
//括号法
A test;
A test1(4);
A test2(test);
//显示法
/*A test = A(4);
A test1 = A(test);
//隐式法
A test = 4;
A test1 = test;*/
}
(2)拷贝构造语法格式:
拷贝构造函数(const 类名& 引用名){ … }
在以下三种情况下拷贝构造函数会自动被调用
1.已经创建完毕的对象初始化一个新的对象。
2.值传递方式给函数传参
3.以值引用方式返回局部对象
如果在函数内部返回局部对象并且与输入参数不相关,则优先调用移动构造。
#include <iostream> //i:input o:output stream:流
#include <string>
//命名空间
using namespace std;
class Pason
{
public:
int Get_num()
{
return this->num;
}
//构造函数基本语法:类名(参数列表) { .. }
Pason()
{
num = 100;
cout<< "普通构造/无参构造"<<endl;
}
//构造函数是可以有参数的,可以发生重载;
Pason(int Num)
{
//数据有效性检验
num = Num;
cout<< "有参构造/普通构造"<<Num<<endl;
}
//拷贝构造函数
Pason(const Pason & a)
{
this->num = a.num;
cout<< "有参构造/拷贝构造函数"<<endl;
}
private:
int num;
};
void test(Pason a) //值方式传参
{
}
Pason test1(Pason & a)//值引用
{
return a;
}
int main()
{
/**************构造函数括号法调用*****************/
Pason a1;
//cout<< "--使用一个已经创建完毕的对象来初始化一个新对象--" <<endl;
//Pason a2 = a1;
//cout<< a2.Get_num() <<endl;
//cout<< "------------值传递的方式给函数参数传值----------" <<endl;
//test(a1);
cout<< "------------------以值方式返回局部对象------------" <<endl;
test1(a1); //g++ 编译器优化
}
(3)拷贝构造函数调用规则:
- 在创建一个类的时候,C++会默认为该类添加至少3个类。默认构造函数(空实现)、默认析构函数(空实现)、默认拷贝函数(浅拷贝)
- 如果用户定义有参构造,编译器不在提供无参构造,但提供拷贝构造。
- 如果用户定义拷贝构造,C++不在提供其他构造。
(4)浅拷贝与深拷贝
浅拷贝:拷贝指针变量的值
深拷贝:拷贝指针所指向内存空间
浅拷贝:当用户没有定义拷贝构造的时候,C++会执行默认拷贝构造函数,进行浅拷贝。直接将原内容的地址交给要拷贝的类,两个类共同指向同一空间。
例如:如果是new开辟空间,const 字符串常量,则同族类实现数据共享。(类似stastic成员属性。因此new一定要深拷贝)。
析构函数不能与浅拷贝连用
深拷贝:过开辟和源空间大小相同的空间并将内容拷贝下来再进行操作。不论是否对s2进行操作,都会拷贝一片相同大小的空间以及内容下来。
#include<iostream>
using namespace std;
class Passon{
public:
inline int Get(void);
inline void Send(int Num);
Passon()
{
// p = new int;
cout<<"无参构造"<<num<<endl;
}
/* Passon(const Passon & passon)
{
this->num = passon.num;
}*/ 深拷贝
int num;
int *p;
private:
};
void Passon::Send(int Num)
{
num = Num;
}
int Passon::Get(void)
{
return num;
}
int main()
{
Passon A;//Passon(10); Passon A = Passon(10)
A.Send(10);
cout<<"Get.num "<<A.Get()<<endl;
Passon B(A);
B.Send(11);
cout<<"Get.num "<<A.Get()<<endl;
cout<<"Get.num "<<B.Get()<<endl;
}
二.析构函数
析构函数:对象销毁前自动调用,执行一些清理工作。在程序执行的过程中,当遇到对象的生存期结束时系统会自动调用析构函数。然后回收为对象分配的存储空间。析构函数不是按照构造的顺序进行析构,且不能进行重载。如果类内有其他成员函数也是先析构本身。
一个对象只能有一个析构函数,但是可以有多个构造函数。且构造函数和析构函数在整个程序运行过程中只执行一次。
格式:
析构函数基本语法:~类名(){ … }(就是在无参构造函数前加~)
#include <iostream> //i:input o:output stream:流
#include <string>
//命名空间
using namespace std;
class Pason
{
public:
int Get_num()
{
return this->num;
}
//构造函数基本语法:类名(参数列表) { .. }
Pason()
{
p = new int;
num = 100;
cout<< "普通构造/无参构造"<<endl;
}
//构造函数是可以有参数的,可以发生重载;
Pason(int Num)
{
//数据有效性检验
num = Num;
cout<< "有参构造/普通构造"<<Num<<endl;
}
//拷贝构造函数
Pason(const Pason & a)
{
this->num = a.num;
cout<< "有参构造/拷贝构造函数"<<endl;
}
//析构函数
~Pason()
{
delete(p);
cout<< "析构函数"<<endl;
}
int * p;
private:
int num;
};
int main()
{
/**************构造函数括号法调用*****************/
Pason a1;
Pason a2(a1);
//cout<<a2.Get_num()<<endl;
}
三.初始化参数列表
初始化参数列表主要是为了解决引用和const修饰变量的初始化。提高程序执行效率
1.格式:
构造函数:属性1(值1),属性2(值2)........
#include<iostream>
using namespace std;
class Pason{
public:
Pason():a(1),b(a),c(2)
{
cout<<"无参构造"<<endl;
cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;
cout<<"c="<<c<<endl;
}
private:
int a;
int &b;
const int c;
};
int main()
{
Pason A;
}
2.构造函数与构造函数初始化列表
构造函数分为初始化阶段和计算阶段。
初始化阶段就是将所有类类型的成员进行初始化,即使该成员没有出现在初始化列表也会被初始化。
构造阶段就是执行构造函数体内的赋值操作。
四.类对象作为类成员
当一个类的数据成员是另一个类的对象时,这个对象称为子对象;子对象可以像普通对象那样使用,唯一要考虑的是子对象构造与析构执行的顺序.
对于含有子类的类,在创建对象的时候:先构造子类,在构造本类。先析构本类,在析构子类。 (好的先儿子,坏的先自己)
格式:
Class A{};
Class B
{
A a;
}
注:B类中有对象作为成员,A作为对象成员;
类嵌套计算:先计算子类各成员变量值,在计算本类成员变量值。加和。
#include<iostream>
using namespace std;
class pason
{
public:
int a;
short b;
};
class Pason
{
public:
//long f; ---8 计算时不会影响别的字节大小
int a; //4
short b; //2-->4
float c;//4
char d;//1-->4
pason e;//8
Pason()//函数在代码区,sizeof不能计算代码区大小
{
string str("aaa");
}
};
int main()
{
cout<<sizeof(Pason)<<endl;
}
六.静态成员
静态成员分为静态成员属性和静态成员函数,静态成员是为了解决数据共享问题。
1.静态成员属性
静态成员属性不是放在栈区,而是像成员函数一样放在类公共区。它不是某个对象中的成员。类静态成员属性也叫类变量
类外定义:函数类型 类名:: 变量名=初值
类内声明:stastic::数据类型 变量名
2.静态成员函数
类外定义:函数类型 类名:: 函数名(参数){}
类内声明:stastic::数据类型 函数名(参数);
#include<iostream>
using namespace std;
class Passon
{
public:
static int a;
};
int Passon::a = 10;
int main()
{
Passon A;
A.a = 11;
Passon B;
B.a = 12;
cout<<A.a<<endl;
}
注意:
- 静态成员函数可以通过创建对象去访问,也可以通过类名::函数名去访问
- 静态成员函数只有访问静态成员属性才有意义,非静态成员属性只有对象存在的时候才有意义。
- 静态成员函数作用域是整个函数,生命周期是整个程序。
- ‘:’用于访问构造函数初始化参数列表。
- ‘:: ’用于作用域访问和静态成员函数
七.This指针
This是一个隐含于每一个类对象的特殊指针,该指针值是一个正在被某个成员函数操作的对象的地址。C++通过提供特殊的对象指针,this指针,解决上述问题。This指针指向被调用的成员函数所属的对象;This指针是隐含每一个非静态成员函数内的指针(静态成员函数中没有this指针);This指针不需要定义,直接使用即可;
this指针用于:函数形参和类成员属性重名。this指向类成员属性。
在类的非静态成员函数中返回对象本身,可使用return *this;
问题:
小明 22;
小明的爸爸差 20;
小明的爷爷 差22;
小明爷爷年龄???
#include<iostream>
using namespace std;
#include<iostream>
using namespace std;
class Passon
{
public:
inline void Add(int a);
inline Passon & get_y(Passon passon);
Passon():a(0)
{
}
//private:
int a;
};
void Passon::Add(int a)
{
this->a = a;
}
Passon & Passon::get_y(Passon passon)
{
this->a = this->a + passon.a;
return *this;
}
int main()
{
Passon test1;
Passon test2;
Passon test3;
Passon test4;
test1.Add(22);
test2.Add(20);
test3.Add(22);
//test4 = test1.get_y(test2).get_y(test3); //将test1,tes2,test3的临时变量都赋值给test4,test4实现累加
//cout<<test4.a<<endl; 输出的是64
test1.get_y(test2).get_y(test3); //所有数值在test1中累加
cout<<test4.a<<endl;
cout<<test1.a<<endl;
cout<<test2.a<<endl;
cout<<test3.a<<endl;
}
八.对象指针
和普通指针一样,我们可以定义一个对象指针。对象指针需要创建它指向的实例,然后通过对象指针操作这个指向的实例。
获得一块自由存储区---堆区
1.new和malloc区别
- malloc、free是标准c/c++的函数。new/delete是c++运算符。
- 都用于申请释放空间,new/delete其实底层也是执行的malloc/free。智能是因为new和delete在对象创建的时候自动执行构造函数,对象消亡之前执行析构函数。(普通类在调用的时候进执行构造/析构函数,而new/delete一个类的时候会在创建/释放的时候执行构造/析构函数)
new可以返回指定类型的指针,自动计算需要开辟的大小。malloc必须指定开辟空间大小,且开辟类型是void *。
2.new用法
()代表赋初始值,[ ]代表开辟大小 。
int *p = new int(100); //开辟一个整数空间,指定整数初始值为100,返回一个指向该存储空间的地址。
char * p =new char[10];//开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址;
float *p=new float (3.14159);//开辟一个存放单精度数的空间,并指定该实数的初值为:3.14159,返回一个指向该存储空间的地址;
class A
{
};
A a = new A; // /开辟一个对象空间
注意:new 一个数组不能指定初始值,但可以执行构造函数进行初始化,如果new失败返回一个NULL,可以判断是否new成功。
九.注意点
1.何时调用构造函数?
在类实例化,不是创建。
在new创建一个类
MyClass c1; MyClass *c2; MyClass *c3 = new MyClass; MyClass & c4 = c1; MyClass c5[3];
上述构造函数调用了5次
创建c1
声明c2,但是没有指向,系统不会分配内存(除了指针开辟的4字节内存),但如果写成MyClass *c2 = new MyClass; 系统分配内存了,会调用构造函数
创建c3
c4只是c1的引用
创建类数组,调用3次构造函数
2.静态成员
静态成员属性被声明为const必须类内初始化,声明为volatile无法初始化
静态成员函数不能被声明为virtual函数.。
静态成员函数和非静态成员函数区别是没有this指针。
3.this指针
递推一定用引用,因为引用可读可写,上一个值引用到下一个值,类似于链表具有指向,在数值计算的中间可以进行传递,而不是一个临时变量。
c1.Add_Age(c2).Add_Age(c3).Add_Age(c4); //如果传值得话会产生副本,是一个临时变量,没有指向。(1+2+3+4 = 3+3+4 = 6+4)//3就是副本
因此使用this指针要注意引用