对象的初始化和清理
*生活中我们买的电子产品基本都会有出场设置,在某一天我们不用的时候也会删除一些自己信息数据安全
*C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置。
构造函数和析构函数
对象的初始化和清理是两个非常重要的问题:
一个对象或者变量没有初始状态,对齐使用后果是未知
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//构造函数:创建对象时为成员属性初始化
class Person
{
// 1.构造函数
// 函数名与类名形同
// 没有void也没有返回值
// 有参数,可以函数重载
// 对象在被创建的时候会自动调用且只会调用一次
// 若没有写构造函数,则系统会自动写构造函数,且默认为public
//Person()
//{
//}
public:
Person()
{
cout << "Person类构造函数调用" << endl;
}
//2.析构函数
//没有返回值,没有void
//函数名与类名相同
//需要在函数名前加 ~
//没有形参,所以也没有重载
//对象在销毁前会自动调用这个析构函数,且只会调用一次
//如果程序员没写析构函数,系统会自动写
//~Person()
//{
//}
~Person()
{
cout << "Person类析构函数调用" << endl;
}
};
void test01()
{
Person p;//在栈上的数据,在test01()函数执行完毕后,就会释放这个对象
}
int main()
{
test01();
Person p;//对象创建,自动调用构造函数
system("pause");
//在函数结束前会自动调用析构函数
return 0;
}
构造函数的分类和调用
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//构造函数的分类
//1.按照参数分类 无参构造 (默认构造) 有参构造
//2.按照类型分类 普通构造 拷贝构造(形参必须是引用形参,加const)
class Person
{
public:
Person()
{
cout << "Person类无参构造函数的调用" << endl;
}
Person(int age)
{
cout << "Person类有参构造函数的调用" << endl;
this->age = age;
}
//拷贝构造函数
Person(const Person& p)
{
cout << "Person类拷贝构造函数的调用" << endl;
this->age = p.age;
}
int age;
~Person()
{
cout << "Person类析构函数的调用" << endl;
}
};
//构造函数的调用
int main()
{
//1. 括号法
Person p1; //Person类无参构造函数的调用
//注意默认构造函数调用 后面不要加()
// 错误的: Person p1();
// 因为编译器会认为这是一个函数声明
Person p2(10); // Person类有参构造函数的调用
Person p3(p2); // Person类拷贝构造函数的调用
p1 = p2;
cout << p1.age << endl;
cout << p2.age << endl;
cout << p3.age << endl;
//2.显示法
// 不知道为什么,在我的电脑上显示法用不了
// 知道了,复制构造函数形参前面要加const
//Person p4;
//Person p5 = Person(10);
//Person p6 = Person(p5);
Person(10);//匿名对象,当前行执行结束后,系统会立即回收调匿名对象
// 编译器会把Person(p3) ===== Person p3 创建变量
cout << "aaaaaaa" << endl;
//3.隐式转换法
Person p7;
Person p8 = 20;
Person p9 = p8;
cout << p8.age << endl;
cout << p9.age << endl;
system("pause");
return 0;
}
拷贝构造函数的调用时机
1.使用一个已经创建完毕的对象来初始化一个对象
2.值传递的方式给函数参数传值
3.以值方式返回局部变量
2,3本质上就是第一种
第三种在本机上与老师讲述的不同,供参考,按理说地址应该时不一样的,在我的电脑上地址一直时一样的
#include<iostream>
using namespace std;
// 拷贝构造函数的调用时机
class Person
{
public:
Person()
{
cout << "Person类默认构造函数的调用" << endl;
}
Person(int age)
{
this->age = age;
cout << "Person类有参构造函数的调用" << endl;
}
Person(const Person& p)
{
this->age = p.age;
cout << "Person类拷贝构造函数的调用" << endl;
}
int age;
~Person()
{
cout << "Person类析构构造函数的调用" << endl;
}
};
//1.使用一个已经创建的对象来初始化一个对象,将会调用拷贝构造函数
void test01()
{
Person p1; // 默认函数调用
Person p2 = p1;// 拷贝函数调用
}
//2.值传递的方式给函数参数传参
void work1(Person p) //值传递
{
// 值传递相当于从从实参那里复制一份到该函数中的p
// 拷贝函数调用
// 相当于从调用函数中拷贝一份p到被调用的函数中
// Person p(被调用的函数中的p) = p(调用函数中的p); 隐式转换法定义方式
}
void test02()
{
Person p(10);// 有参函数调用
work1(p);
}
//3.以值的方式返回局部变量
Person work2()
{
Person p;//有参函数调用
cout << (int)&p << endl;
return p;
}
void test03()
{
Person p = work2(); //类似于隐式转换法的定义方式
// Person p1 = p2;
cout << (int)&p << endl;
}
int main()
{
//test01();
//test02();
test03();
system("pause");
return 0;
}
构造函数的调用规则
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
/*
默认情况下,C++编译器至少给一个类添加三个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
*/
class Person
{
public:
//Person()
//{
// cout << "Person类无参构造函数的调用" << endl;
//}
//Person(int age)
//{
// cout << "Person类有参构造函数的调用" << endl;
// this->age = age;
//}
Person(const Person& p)
{
this->age = p.age;
cout << "Person类拷贝构造函数的调用" << endl;
}
int age;
~Person()
{
cout << "Person类析构函数的调用" << endl;
}
};
//构造函数调用规则如下
//1.
//如果用户定义了有参构造函数,C++不在提供默认构造参数,但会提供默认拷贝构造函数
//void test01()
//{
// //Person p;
// Person p1(10);
// Person p2(p1);
// cout << p2.age << endl;
//}
//2
//如果用户定义了拷贝构造函数,C++不会提供其它构造函数
void test02()
{
}
int main()
{
//test01();
system("pause");
return 0;
}
深拷贝与浅拷贝
深拷贝:在堆区重新申请空间,进行拷贝操作
浅拷贝:简单的赋值拷贝操作,比如类中的拷贝构造函数简单的赋值拷贝,编译器提供的拷贝构造函数都叫浅拷贝,本质也就是等号赋值操作
浅拷贝带来的问题就是堆区的内存重复释放
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//深拷贝与浅拷贝
class Person
{
public:
Person()
{
cout << "Person类默认构造函数的调用" << endl;
}
Person(int age,int height)
{
this->age = age;
// 在堆区开辟的空间,需要程序员手动释放
this->height = new int(height);
cout << "Persn有参构造函数的调用" << endl;
}
int age;
// 这是一个地址,存储地址,在类中并不会自动开辟地址,需要手动开辟地址(个人理解)
int* height;
~Person()
{
// 析构代码:将堆区开辟的数据做释放操作
if (height != NULL)
{
delete height;// 释放空间
height = NULL;
}
cout << "Person类析构函数的调用" << endl;
}
};
void test01()
{
Person p1(10,163);
cout << "p1的年龄为:" << p1.age << " p1的身高为:" << *(p1.height) << endl;
Person p2(p1);
// 在栈区,先进后出,后进先出,p2先被释放,所以p2先调用析构函数释放空间
// 之后p1就无法在重复释放同一块空间了,没有权限,
// 释放空间也可以理解为原先指向该空间的指针全部作废,成为自由无指向的空间(个人理解)
cout << "p2的年龄为:" << p2.age << " p2的身高为:" << *(p2.height) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
浅拷贝的问题要用深拷贝来解决.
如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//深拷贝与浅拷贝
class Person
{
public:
Person()
{
cout << "Person类默认构造函数的调用" << endl;
}
Person(int age,int height)
{
this->age = age;
this->height = new int(height);
cout << "Persn有参构造函数的调用" << endl;
}
// 自己实现拷贝构造函数来结局浅拷贝所带来的问题
Person(const Person& p)
{
cout << "Person类拷贝构造函数的调用" << endl;
this->age = p.age;
// 编译器写的代码是:
// this -> height = p.height;
this->height = new int(*(p.height));// 开辟空间的同时也在赋值
}
int age;
// 这是一个地址,存储地址,在类中并不会自动开辟地址,需要手动开辟地址(个人理解)
int* height;
~Person()
{
// 析构代码:将堆区开辟的数据做释放操作
if (height != NULL)
{
delete height;
height = NULL;
}
cout << "Person类析构函数的调用" << endl;
}
};
void test01()
{
Person p1(10,163);
cout << "p1的年龄为:" << p1.age << " p1的身高为:" << *(p1.height) << endl;
Person p2(p1);
cout << "p2的年龄为:" << p2.age << " p2的身高为:" << *(p2.height) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
初始化列表
主要用途: 对类中的属性进行初始化的操作
构造函数(): 属性1(值1),属性2(值2).....{};
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Person
{
public:
//传统的初始化方式
//Person(int a,int b, int c)
//{
// this->a = a;
// this->b = b;
// this->c = c;
//}
// 低阶初始化列表,只能初始化一次
Person() :a(10), b(20), c(30)
{
}
// 高阶初始化列表
// 括号外的a作用域是此函数体外的,括号内的作用域是此函数内的
Person(int a, int b, int c) :a(a), b(b), c(c)
{
}
int a;
int b;
int c;
};
void test01()
{
//Person p(10, 20, 30);
//Person p;
Person p(30, 20, 10);
cout << "a = " << p.a << endl;
cout << "b = " << p.b << endl;
cout << "c = " << p.c << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
当其它类对象作为本类对象,构造时先构造类对象,再构造自身。(现有肢体,再有整体)
析构的顺序与构造的顺序相反,(人性化:先杀人,再解肢)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
// 类对象作为类成员
class Phone
{
public:
string pName;
Phone(string name) :pName(name)
{
cout << "Phone类有参构造函数的调用" << endl;
}
~Phone()
{
cout << "Phone类析构函数的调用" << endl;
}
};
class Person
{
public:
string name;
Phone phone;
// phone(pName) 相当于:
// Phone phone = pName 相当于 Phone phone(pName);
// 对象的创建:隐式转换法
Person(string name, string pName) :name(name), phone(pName)
{
cout << "Person类有参构造函数的调用" << endl;
}
~Person()
{
cout << "Person类析构函数的调用" << endl;
}
};
void test01()
{
/*
Phone类有参构造函数的调用
Person类有参构造函数的调用
张三拿着三星
Person类析构函数的调用
Phone类析构函数的调用
*/
Person p("张三", "三星");
cout << p.name << "拿着" << p.phone.pName << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
静态成员
类中含有成员属性和成员函数
静态成员变量
1.所有对象共享同一份数据
2.在编译阶段分配内存
3.类内声明,类外初始化
4.静态成员变量也是有访问权限的,private类型类外不可以访问,但是可以类外初始化操作
静态成员变量不属于某个对象,是共有的,所有对象都共享同一份数据
因此静态成员变量有两种访问方式
1.通过对象进行访问
2.通过类名进行访问
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//静态成员变量
//1. 所有对象共享同一份数据
//2. 在编译阶段分配内存
//3. 类内声明,类外初始化
static int b;
class Person//相当于定义了一个类型
{
public:
static int a;
private:
static int b;
};
// :: 代表作用域
int Person::a = 10;
int Person::b = 20;
void test01()
{
Person p1;
cout << p1.a << endl;
Person p2;
p2.a = 20;
cout << p1.a << endl;
}
void test02()
{
//静态成员变量不属于某个对象,是共有的,所有对象都共享同一份数据
// 因此静态成员变量有两种访问方式
// 1.通过对象进行访问
Person p;
cout << p.a << endl;
// 2.通过类名进行访问
cout << Person::a << endl;
//4.静态成员变量也是有访问权限的,private类型类外不可以访问,但是可以类外初始化操作
//cout << Person::b << endl;
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
静态成员函数
1.所有对象共享同一个函数
2.静态成员函数只能访问静态成员变量
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//静态成员函数
class Person
{
public:
static void func()
{
a = 200; // 静态成员函数可以访问静态成员变量
// 因为每个对象共享这个静态成员函数,而非静态成员变量属于某个特定的对象的
// 故函数体无法区分给哪个对象的b赋值
// b = 10; // 静态成员函数不可以访问非静态成员变量
cout << "static void func()函数的调用" << endl;
}
static int a;// 静态成员变量
int b;// 非静态成员变量
private:
//静态成员函数也是有访问权限的
static void func2()
{
cout << "static void func2()的调用" << endl;
}
};
int Person::a = 0;
//如何访问静态成员函数
void test01()
{
// 1.通过创建对象来访问
Person p;
p.func();
// 2.通过类名来访问
// 通过类名就可以访问就说明它不属于某一个对象,每一个对象都在共享同一个静态成员函数,
// 所以不需要创建对象就可以访问这个静态成员函数
Person::func();
//Person::func2(); // 类外访问不到私有的静态成员函数
}
int main()
{
test01();
system("pause");
return 0;
}
C++对象模型和this指针
成员变量和成员函数分开存储
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
// 1.只有非静态成员变量才属于类的对象上
// 2.空对象占用内存1字节
class Person
{
int a;//非静态成员变量是属于类的对象上的
static int b;//静态成员变量不属于类的对象上
void func1() //非静态成员函数不属于类的对象上
{
}
static void func2()//静态成员函数不属于类的对象上
{
}
};
int Person:: b = 0;
void test01()
{
Person p;
// 空对象占用的内存空间为1
// C++编译器会给每个空对象分配一个字节空间,是为了区分空对象在内存中的位置
// 每个空对象也应该有一个独一味二的内存地址
cout << sizeof(p) << endl;
}
void test02()
{
Person p;
cout << sizeof(p) << endl;
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
this指针的用途
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
// 1.解决名称冲突
// 2.返回对象本身 return *this;
class Person
{
public:
int age;
Person(int age)
{
// this指针指向 被调用的成员函数 所属的对象
this->age = age;
}
//注意值传递和址传递和引用传递,如果将&去掉,结果将不同
Person& personAddAge(Person& p)
{
this->age += p.age;
return *this;
}
};
void test01()
{
// p被创建,同时调用有参构造函数(成员函数)
// 该成员函数属于p,故this指向p
Person p(18);
cout << p.age << endl;
}
void test02()
{
Person p1(10);
Person p2(10);
// 链式编程思想
p2.personAddAge(p1).personAddAge(p1).personAddAge(p1).personAddAge(p1);
cout << p2.age << endl;
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
空指针访问成员函数
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Person
{
public:
void showClassName()
{
cout << "这是一个Person类" << endl;
}
void showClassAge()
{
// 防止代码出错
// 提高代码的健壮性
if (this == NULL)
{
return;
}
// 在属性前面都默认加了 this-> ,告诉你这是当前对象的属性
// 但是此时this指针指向p , p是一个空指针 ,没有指向一个确切的对象
// 故出错
cout << "age = " << m_Age << endl;
}
int m_Age;
};
void test01()
{
Person* p = NULL;
p->showClassName();
p->showClassAge();
}
int main()
{
test01();
system("pause");
return 0;
}
const修饰成员函数(常函数与常对象 )
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
// 1.常函数
class Person
{
public:
// this的本质是指针常量 Person * const this 指针的指向是不可以被修改的
// 在成员函数后面加const ,目的是让指针指向的值也不可以被修改,const Person * const this
void showPerson() const // 这个函数称为常函数
{
//this->a = 100;
this->b = 20;//因为b有关键字mutable修饰 mutable(可变的)
}
void func()
{
a = 20;
}
int a;
mutable int b;
};
void test01()
{
Person p;
p.showPerson();
cout << p.b << endl;
}
//2.常对象
// 常对象只能调用常函数
void test02()
{
const Person p;
//p.a = 10;
p.b = 100;// 因为b被mutable(可变的)关键词修饰,所以在常对象下也可以被修改
cout << p.b << endl;
p.showPerson();//常对象只能调用常函数
cout << p.b << endl;
//因为在普通(非静态,非常量)的成员函数是可以修改成员属性的
//而常对象不能修改其未被mutable修饰的成员属性的值
//故常对象不可以调用普通成员函数(成员函数不属于对象本身)
//p.func();
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}