c++小白有感于类和对象的复杂,学习b站黑马程序员的相关视频后,发文以总结。
对象的初始化和清理
构造函数和析构函数
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要作用于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
1、构造函数没有返回值也不用写void;
2、函数名称与类名相同;
3、构造函数可以有参数,因此可以发生重载;
4、程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次。
析构函数语法:~类名(){}
1、析构函数没有返回值也不用写void;
2、函数名称与类名相同,在名称前加~;
3、析构函数不可以与参数,因此不可以发生重载;
4、程序在对象销毁前会调用析构函数,无须手动调用,而且只会调用一次。
#include<iostream>
using namespace std;
class Person{
public:
//构造函数
//1、构造函数没有返回值也不用写void;
//2、函数名称与类名相同;
//3、构造函数可以有参数,因此可以发生重载;
//4、程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次。
Person(){
cout<<"Person 构造函数的调用"<<endl;
}
//析构函数
//1、析构函数没有返回值也不用写void;
//2、函数名称与类名相同,在名称前加~;
//3、析构函数不可以与参数,因此不可以发生重载;
//4、程序在对象销毁前会调用析构函数,无须手动调用,而且只会调用一次。
~Person(){
cout<<"Person 析构函数的调用"<<endl;
}
//构造函数和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构。
};
void test01(){
Person p;
}
int main(){
test01();
system("pause");
return 0;
}
构造函数的分类及调用
两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
三种调用方式
括号法、显示法、隐式转换法
#include<iostream>
using namespace std;
class Person{
public:
int age;
Person(){
cout<<"Person的无参构造函数的调用"<<endl;
}
Person(int a){
age=a;
cout<<"Person的有参构造函数的调用"<<endl;
}
//拷贝构造函数
Person(const Person &p){
//拷贝传入的对象信息;
age=p.age;
}
};
void test01(){
//括号法
Person p1;
Person p2(10);
person p3(p2);//括号法调用拷贝构造
//注意事项:调用默认构造函数(无参构造)时,不要加()。
//显示法
Person p4=Person(10);//有参构造
Person p5=Person(p4);//拷贝构造
//注意事项:不要利用拷贝构造初始化匿名对象
Person(10);//匿名对象 当前行执行结束后,系统立即回收
//隐式转换法
Person p6=10;//相当于Person p6=Person(10)
Person p7=p6;//拷贝构造
}
int main(){
test01();
system("pause");
return 0;
}
拷贝构造函数调用时机
三种调用拷贝函数的情况:
1、使用一个已经创建完毕的对象来初始化新对象(常用);
2、值传递方式给函数参数传值;
3、以值方式返回局部对象。
#include<iostream>
using namespace std;
class Person{
public:
int m_Age;
person(){
cout<<"Person默认构造函数调用"<<endl;
}
person(int age){
m_Age=age;
cout<<"Person拷贝构造函数被调用"<<endl;
}
Person(const Person & p){
m_Age=p.m_Age;
}
~Person(){
cout<<"Person析构函数调用"<<endl;
}
};
// 1、使用一个已经创建完毕的对象来初始化新对象(常用)
void test01(){
Person p1(20);
person p2(p1);
cout<<"p2的年龄为:"<<p2.m_Age<<endl;
}
// 2、值传递方式给函数参数传值;
void dowork(Person a){
}
void test02(){
Person p;
dowork(p);
}
// 3、以值方式返回局部对象。
Person Dowork(){
Person p1;
return p1;
}
void test03(){
Person p=Dowork();
}
int main(){
test01();
test02();
test03();
system("pause");
return 0;
}
构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1、默认构造函数(无参,函数体为空);
2、默认析构函数 (无参,函数体为空);
3、默认拷贝函数,对属性进行值拷贝。
构造函数调用规则如下:
1、如果用户定义有参构造函数,c++不在默认无参构造,但是会提供默认拷贝构造;
2、如果用户定义拷贝构造函数,c++不会提供其他构造函数。
#incldue<iostream>
using namespace std;
class Person{
//默认情况下,c++编译器至少给一个类添加3个函数
//1、默认构造函数(无参,函数体为空);
//2、默认析构函数 (无参,函数体为空);
//3、默认拷贝函数,对属性进行值拷贝。
//构造函数调用规则如下:
//1、如果用户定义有参构造函数,c++不在默认无参构造,但是会提供默认拷贝构造;
//2、如果用户定义拷贝构造函数,c++不会提供其他构造函数。
public:
int m_Age;
Person(){
cout<<"Person无参构造函数调用"<<endl;
}
Person(int Age){
m_Age=Age;
cout<<"Person有参构造函数调用"<<endl;
}
~Person(){
cout<<"Person析构函数调用"<<endl;
}
//Person(const Person &p){
//cout<<"Person拷贝构造函数调用"<<endl;
//m_Age=p.m_Age;
//}
};
void test01(){
Person P1;
p1.m_Age=18;
Person P2(P1);
cout<<"P2的年龄为:"<<P2.m_Age<<endl;
}
int main(){
test01();
system("pause");
return 0;
}
深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作;
深拷贝:在堆区重新申请空间,进行拷贝操作。
#incldue<iostream>
using namespace std;
class Person{
public:
int m_Age;
int *m_Height;
Person(){
cout<<"Person无参构造函数调用"<<endl;
}
Person(int Age,int Height){
new int(height);
m_Height=new int(height);
m_Age=Age;
cout<<"Person有参构造函数调用"<<endl;
}
~Person(){
//析构代码,将堆区开辟的数据释放
if(m_Height !=NULL){
delete m_Height;
m_Height=NULL;
}
cout<<"Person析构函数调用"<<endl;
}
Person(const Person &p){
cout<<"Person拷贝构造函数调用"<<endl;
m_Age=p.m_Age;
}
};
void test01(){
Person P1(18,173);
cout<<"P1的年龄为:"<<P1.m_Age<<endl;
cout<<"P1的身高为:"<<*P1.m_Height<<endl;
Person P2(P1);
cout<<"P2的年龄为:"<<P2.m_Age<<endl;
cout<<"P1的身高为:"<<*P2.m_Height<<endl;
}
int main(){
test01();
system("pause");
return 0;
}
以上代码会报错,因为浅拷贝拷贝的代码被重复释放,即堆区代码被释放了两次。
以下为深拷贝解决此问题
#incldue<iostream>
using namespace std;
class Person{
public:
int m_Age;
int *m_Height;
Person(){
cout<<"Person无参构造函数调用"<<endl;
}
Person(int Age,int Height){
new int(height);
m_Height=new int(height);
m_Age=Age;
cout<<"Person有参构造函数调用"<<endl;
}
//自己实现拷贝构造函数来解决浅拷贝带来的问题
Person(const Person &p){
cout<<"Person 拷贝构造函数调用"<<endl;
//m_Height=p.m_Height 编译器默认实现此行代码即浅拷贝
//深拷贝操作
m_Height=new int(*p.m_Height);
}
~Person(){
//析构代码,将堆区开辟的数据释放
if(m_Height !=NULL){
delete m_Height;
m_Height=NULL;
}
cout<<"Person析构函数调用"<<endl;
}
Person(const Person &p){
cout<<"Person拷贝构造函数调用"<<endl;
m_Age=p.m_Age;
}
};
void test01(){
Person P1(18,173);
cout<<"P1的年龄为:"<<P1.m_Age<<endl;
cout<<"P1的身高为:"<<*P1.m_Height<<endl;
Person P2(P1);
cout<<"P2的年龄为:"<<P2.m_Age<<endl;
cout<<"P1的身高为:"<<*P2.m_Height<<endl;
}
int main(){
test01();
system("pause");
return 0;
}
初始化列表
作用:
c++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)等等{}
#incldue<iostream>
using namespace std;
class Person
{
public:
int m_A;
int m_B;
int m_C;
//Person(int a,int b,int c){
// m_A=a;
// m_B=b;
// m_c=c;
//}传统方式
Person(int a,int b,int c):m_A(a),m_B(b),m_C(c){
}//采用初始化列表方式
};
void test01(){
//Person p(10,20,30);
Person p(30,20,10);
cout<<"m_A=:"<<p.m_A<<endl;
cout<<"m_B=:"<<p.m_B<<endl;
cout<<"m_C=:"<<p.m_C<<endl;
}
int main(){
test01();
system("pause");
return 0;
}
类对象作为类成员
c++类中的成员可以是另一个类的对象,我们称该成员为对象成员。
#include<iostream>
#include<string>
using namespace std;
class Phone{
public:
string m_PName;
Phone(string PName){
m_PName=PName;
}
}
class Person{
public:
string m_Name;
Phone m_Phone;
Person(string name,string PName):m_Name(name),m_Phone(PName){
}
};
void test01(){
Phone P("张三","华为Nova7");
cout<<p.m_Name<<"拿着"<<p.m_Phone.m_PName<<endl;
}
int main(){
test01();
system("pause");
return 0;
}
静态成员
静态成员变量:
所有对象共享同一个数据;
在编译阶段分配内存;
类内声明,类外初始化。
静态成员函数:
所有对象共享一个函数;
静态成员函数只能访问静态成员变量。
#include<iostream>
using namespace std;
//静态成员函数
//所有对象共享一个函数;
//静态成员函数只能访问静态成员变量。
class PersonP{
public:
//静态成员函数
static void func(){
m_A=100;
//m_B=10 静态成员函数不可以访问非静态成员变量
cout<<"静态成员函数被调用"<<endl;
}
static int m_A;//静态成员变量
int m_B;
//静态成员函数也是有访问权限的
private:
static void fun2(){]
};
Person::m_A=0;
void test01(){
//1、通过对象访问
Person p;
p.func();
//p.fun2();
//2、通过类名访问
Person::func();
//Person::fun2();
}
int main(){
system("pause");
return 0;
}
成员变量和成员函数分开存储
在c++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上。
#incldue<iostream>
using namespace std;
//成员变量和成员函数分开存储
class Person{
int m_A;//非静态成员变量,属于类对象上
static int m_B;//静态成员变量,不属于类对象上
//void func(){}//非静态成员函数,属于类对象上
//static void fun(){}//静态成员函数,不属于类对象上
};
void test01(){
Person p;
//空对象占用内存空间为:1
//c++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置。
//每个空对象都有一个独一无二的内存地址
cout<<"size of p"<<sizeof(p)<<endl;
}
void test02(){
Person p;
cout<<"size of p"<<sizeof(p)<<endl;
}
int Person::m_B=0;
int main(){
test01();
system("pause");
return 0;
}
this指针
成员变量和成员函数是分开存储的,每一个非静态成员函数只会但是一份函数实例,也就是说多个同类型的对象会公用一块代码,那么这一块代码是如何区分哪个对象调用自己呢?
c++通过this指针解决上述问题,this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非成员函数内的一种指针,不需要定义,直接使用。
this指针的用途:
当形参和成员变量同名时,用this指针来区分;
在类的非静态成员函数中返回对象本身。
#include<iostream>
using namespace std;
//1、解决名称冲突
//2、返回对象本身用*this
class Person{
public:
//int m_Age;
//Person(int age){
//m_Age=age;
//}
int age;
Person(int age){
this->age=age;//当形参和成员变量同名时,用this指针来区分
}
Person& PersonAddAge(Person&p){
this->age+=p.age;
//this是指向p2.age的指针,那么*this指向本体。
return *this;
}
};
void test01(){
Person p1(18);
//cout<<"p1的年龄:"<<p1.m_Age<<endl;
cout<<"p1的年龄:"<<p1.age<<endl;
}
void test02(){
Person p2(10);
Person p3(10);
//p3.PersonAddAge(p2);
//链式编程思想
p3.PersonAddAge(p2).PersonAddAge(p2).PersonAddAge(p2);
cout<<"p3的年龄为:"<<p3.age<<endl;
}
int main(){
test01();
test02();
system("pause");
return 0;
}
空指针访问成员函数
c++中空指针也可以调用成员函数,但要注意有没有用this指针
如果用到this指针,需要注意代码的健壮性
#include<iostream>
using namespace std;
class Person{
public:
int m_Age;
void showClassName(){
cout<<"this is Person class"<<endl;
}
void showPersonAge(){
if(this==NULL){
return 1;
}
cout<<"age=:"<<m_Age<<endl;//报错原因是传入指针为NULL;
}
};
void test01(){
Person *p=NULL;
p->showClassName();
//p->showPersonAge();
}
int main(){
test01();
system("pause");
return 0;
}
const修饰成员函数
常函数:
1、成员函数后加const后称为常函数
2、常函数内一般情况下不可修改成员属性
3、成员属性声明时加mutable后,在常函数中依然可以修改。
常对象:
1、声明对象前加const称常对象;
2、常对象只能调用常函数。
#include<iostream>
using namespace std;
class Person{
public:
//this指针的本质是指针常量,指向不可以修改
int m_A;
mutable int m_B;//常函数中可以被修改的变量
void showPerson()const{
//此时成员属性不能修改
//m_A=100;
this->m_B=100;
}
void a(){};
};
void test01(){
Person p;
p.showPerson();
}
void test02(){
const Person p;//常对象
//p.m_A=100;
p.m_B=100;
//常对象只能调用常函数,普通对象即能调用常函数,也能调用非常函数
p.showPerson();
//p.a
//常对象不可以调用普通成员函数,因为普通成员函数可以修改属性
}
int main(){
test01();
system("pause");
return 0;
}
小总结:常函数和常变量主要用来保护对象属性