C++类和对象学习笔记2

类和对象拷贝构造函数的调用时机

c++中拷贝构造函数调用时机通常有三种情况

1.使用一个已经创建完毕的对象来初始化一个新对象

2.值传递的方式给函数参数传值

3.以值方式返回局部对象

#include<iostream>

using namespace std;

class person

{

public:

        person()

        {

                cout<<"person默认构造函数调用"<<endl;

        }

        

         person(int age)

        {

                m_age=age;

                cout<<"person有参构造函数调用"<<endl;

        }

        person(const person &p)

        {

                m_age=p.m_age;

                cout<<"person拷贝构造函数的调用"<<endl;

        }

        ~person()

        {

                cout<<"person析构函数调用"<<endl;

        }

        int m_age;

};

我们在这里创建了一个person类,接下来,我会演示每一种调用的例子。

完毕的对象来初始化一个新对象

void test1()

{

person p1(20);

personp2(p1);

}

这里我们创建了两个person对象,我们利用p1的有参构造,给p1的m_age进行赋值操作,

接下来我们就可以使用一个已经创建完毕的对象p1来初始化一个新对象p2。我们打印p2的age值就可以得到和p1一样的age值。

值传递

void chuandi(person p)

{

}

void test2()

{

person p;

chuandi(p);

}

如果我们运行这个test2,我们会发现我们这里调用了一次拷贝构造函数和两次析构函数,我们这时候可以这么理解:我们先创建了一个对象p,然后我们调用chuandi函数的时候又产生了一个形参(我理解为虚拟对象),所以说chuandi(p)和void chuandi (person p)的p不是一个p,这个函数的作用就是把p的值传递给另一个对象。相当于隐式转换法(person p=p)。(我是这么理解的不对的地方请在评论区大家指出)。

这就是第二中调用情况,值传递。

值方式返回局部对象

person dowork()

{

person p3;

return p3;

}

void test3()

{

person p4=dowork();

}

在通常情况下我们运行这个test3函数我们会看到这里调用了无参构造函数,拷贝构造函数,两簇析构函数 ,这里的返回值不会返回p3,而是会拷贝一次p3,然后再把值传递给p4。按正常情况是这样的,但是我们在使用vs2022等编译器时,发现并没有出现拷贝函数的调用而且析构函数只调用了一次,实际上这个拷贝函数是有被调用的,我看了其他博主,他们提到这是由于编译器的ROV优化,至于这个具体的机制我没看懂,大家可以百度一下。所以我们还是按照原来的理解方式去理解。

当然我们也可以通过打印m_age的地址去验证这个思路,当然在vs2022等编译器里我们还是会发现出来了两个相同的地址,而按我们的理解是会出现两个不同的地址的。

深拷贝与浅拷贝

简单来说,浅拷贝就是进行简单的赋值操作,比如编译器默认提供的拷贝函数就是一种浅拷贝;深拷贝就是在栈区重新申请一块空间,进行拷贝操作。

浅拷贝

#include<iostream>
using namespace std;
class person
{
public:
	int age;
	int* high;
	person()
	{
		cout << "person无参构造函数调用" << endl;
	}
	person(int a,int b)
	{
		age = a;
		high = new int(b);
		cout << "person有参构造函数调用" << endl;
	}
	~person()
	{
        if(high!=NULL)
        {
            delete high;
            high=NULL;
        }
		cout << "析构函数调用" << endl;
	}
};
void test1()
{
	person p1(18,180);
	person p2(p1)
}

这上面我们使用了person的默认拷贝构造函数,它的默认构造函数其实相当于写了

age=p1.age;

high=p.high;

我们这里开辟了一个栈区,这时候我们需要编写析构函数进行栈区释放,这是一个需要注意的点,如果我们去运行这段代码,我们会发现出错。接下来我们解释一下原因。

这里其实是进行了简单的值拷贝,我觉得其实浅拷贝就像值传递,而 深拷贝相当于地址传递,当然这种说法不是很准确。如果我们这里对p1,p2的high地址进行打印,我们会发现他们的地址是一样的,所以我们这里调用析构函数时会对这栈区进行两次释放,这显然就是报错的原因。析构函数调用遵循先进后出,这里先对p2的high进行释放,然后p1再调用了一次析构函数,导致多次对同一栈区进行内存释放,导致出错。

深拷贝

#include<iostream>
using namespace std;
class person
{
public:
	int age;
	int* high;
	person()
	{
		cout << "person无参构造函数调用" << endl;
	}
	person(int a, int b)
	{
		age = a;
		high = new int(b);
		cout << "person有参构造函数调用" << endl;
	}
	person(const person& p)
	{
		age = p.age;
		high = new int(*p.high);
	}
	~person()
	{
         if(high!=NULL)
        {
            delete high;
            high=NULL;
        }
		cout << "析构函数调用" << endl;
	}
};
void test1()
{
	person p1(18, 180);
	person p2(p1);
	cout << p1.age << "   " << p1.high << endl;
	cout << p2.age << "   " << p2.high << endl;
}
int main()
{
	test1();
	system("pause");
	return 0;
}

我们知道默认的拷贝构造函数是浅拷贝,所以这里需要我们重新写一个深拷贝,在这里age直接进行值拷贝是没有问题的,但是这个指针直接进行拷贝就会出现我们刚才的问题,所以我们这里用new int *开辟了一块新的空间,这样p2的high地址就和p1的不同,我们在调用析构函数的时才不会出错。

初始化列表

在C++中有一种语法可以初始化属性,叫初始化列表。它的语法很简单:构造函数():+属性1(初始化的值),属性2()·······

接下来我们演示一些他的用法。

class son
{
public:
	int m;
	int l;
	int g;
	son(int a, int b, int c) :m(a), l(b), g(c) 
	{

	}
};
void test1()
{
	son p(10,20, 30);
}

我们这里完成了对对象son属性的初始化。

类对象作为类成员

C++类中的成员可以是另一个类的对象,我们称该成员为对象成员

例如:class A{};

class B{ A a};

这里就是一个最简单的对象成员,A类对象a作为B类的成员。这里我们会产生疑惑,A与B的构造函数和析构函数的顺序是谁先谁后?我们用一段代码来寻找这个问题的答案。

#include<iostream>
using namespace std;
#include<string>
class phone
{
public:
	phone(string s)
	{
		cout << "phone 构造函数的调用" << endl;
		phoneid = s;
	}
	string phoneid;
	~phone()
	{
		cout << "phone析构函数调用" << endl;
	}
};
class person
{
public:
	person(string a, string b) :name(a), m_phone(b)
	{
		cout << "person构造函数调用" << endl;
	}
	string name;
	phone m_phone;
	~person()
	{
		cout << "person析构函数调用" << endl;
	}
};
void test1()
{
	person p("李四","iphoneX");
	cout << p.name << "的手机是" << p.m_phone.phoneid << endl;
}
int main()
{
	test1();
	system("pause");
	return 0;
}

我们运行这段代码我们会得到:李四的手机是iphoneX以及一系列的函数调用。这里m_phone(b)用到了隐式转换法,相当于phone m_phone=b。我们会看到这里的phone构造函数先被调用,再调用了person构造函数,这说明类成员的构造函数会先于类对象调用,然后person的析构函数先被调用,再调用phone的析构函数。这里我把类成员理解为零件,类对象理解为一台完整的机器,在组装机器时,要先组装完零件,再组装机器,拆解机器时先把整体拆出来,再对零件进行拆解。

总结:类成员构造函数调用先于类对象,类对象析构函数调用先于类成员。

静态成员

静态成员是指在成员变量和成员函数前加上关键字static,称为静态成员。

静态成员分为:

1.静态成员变量

特征:所有对象共享同一份数据

           在编译阶段分配内存

            类内声明类外初始化

2.静态成员函数

特征:所有对象共享一个函数

           静态成员函数只能访问静态成员变量 

接下来简单演示一下他们的用法:

#include<iostream>
using namespace std;
#include<string>
class person
{
public:
	static int a;
};
int person::a = 100;
void test1()
{
	person p1;
	cout << p1.a << endl;
	person p2;
	p1.a = 200;
	cout << p2.a << endl;
}

因为静态成员不属于任何一个对象,所以可以有两种访问方式,一种是通过访问对象,一种是通过访问类名。这里可以用person::a来访问。我们运行一下这段代码,可以发现p1的a值为100,p2的a值为200,这说明a的值会被覆盖。

#include<iostream>
using namespace std;
#include<string>
class person
{
public:
	static void fanc()
	{
		a = 100;
		cout << "static void fanc调用" << endl;
	}
	static int a;
};
int person::a = 0;
void test1()
{
	person p1;
	p1.fanc();
}
int main()
{
	test1();
	cout << person::a << endl;
	system("pause");
	return 0;
}

  静态成员函数和静态成员变量非常相似,只需要注意静态成员函数只能调用静态成员变量就行了。静态成员函数和静态成员变量都需要注意权限问题。

C++对象模型和this指针

在C++中,类内成员和类内函数是分开储存的,只有非静态成员变量才属于类的对象上。                这里我们很好去验证,我们只需要将各种对象的占用内存情况进行分析就可以得知

class person{};

test1(){person p1;

seziof(p1) ;}    

 class person{

int a;

};

test1(){person p1;

seziof(p1) ;}    

 class person{

int a;

void fanc();

};

test1(){person p1;

seziof(p1) ;}    

我们只需要计算这几个字节长度就可以发现分别是1,4,4;这里第二个注意一下是4,而不是5,然后我们发现确实只有非静态成员变量属于该对象 。

在c++中每一个非静态成员函数只会诞生一份函数实例,多个同类型对象会共用一块代码,c++是怎么知道哪个对象在调用自己的呢?

C++通过提供特殊的对象指针this指针,this指针指向被调用的成员函数所属的对象。

注意:this指针隐含在每一个非静态成员函数中,不需要声明直接引用就可以。

用途:当形参和变量名 同名时,可以用this指针来区分;在类的非静态成员函数中返回对象本身,可使用return *this。

#include<iostream>
using namespace std;
class person
{
public:
	person(int age)
	{
		this->age = age;
	}
	int age;
};
void test()
{
	person p1(10);
}

这里我们如果直接写age=age我们会得到一个乱码,用this指针很好地区分了这两个变量。

#include<iostream>
using namespace std;
class person
{
public:
	person(int age)
	{
		this->age = age;
	}
	person& add(person& p)
	{
		this->age += p.age;
		return *this;
	}
	int age;
};
void test()
{
	person p1(10);
	person p2(10);
	p2.add(p1).add(p1).add(p1);
	cout << p1.age << endl;
	cout << p2.age << endl;
}

这里add这个函数内this是指向p2的指针,而*this指向的是p2的本体,所以我们这里返回了p2这个对象,如果我们这里把person& add(person& p)第一个&去掉(解引用),我们就会发现p2的age值变成了20,这是因为我们这里返回了一个值,这里相当于用了拷贝构造,创造了p2‘,p2’‘,而不是不继续,只是指向了另一个内存。

空指针访问成员函数

c++中空指针也是可以调用成员函数的,但是要注意this指针的使用,如果用到this的指针需要用判断来保证代码的健壮性。

#include<iostream>
using namespace std;
class person 
{
public:
	void showname()
	{
		cout << "this is person class" << endl;
	}
	int age;
	void showage()
	{
		cout << age << endl;
	}
};
void test1()
{
	person* p = NULL;
	p->showname();
	p->showage();
}

这里我们的代码是无法运行的,因为这里的空指针使用了this,在showage中其实age是this->age,这里的this是空指针,所以编译器不知道调用的是哪个age。为了使我们的代码更健壮,showage可以加

if(this==NULL)return;

这样就不会报错了。

const修饰成员函数

常函数:

成员函数后加const就叫常函数;常函数内不可以修改成员属性;成员属性声明时加mutable后可以在常函数中修改 。

常对象:

声明对象前加const;常对象只能调用常函数。

#include<iostream>
using namespace std;
class person 
{
public:
	void showperson()const
	{
		this->b = 100;
	}
	mutable int b;
};
void test1()
{
	person p1;
}

在这段代码中showperson是不能修改没有mutable前缀的变量的,这是因为this本质是一个指针常量,如果在成员函数后面加const,就相当于把this的指向也锁定,无法指向其他,所以没办法改变。

class person 
{
public:
	void showperson()const
	{
		this->b = 100;
	}
	mutable int b;
};
void test2()
{
	 const person p2;
	 p2.b = 100;
	 p2.showperson();
}

在常对象中,只能引用常函数,常对象中mutable变量还是可以被修改的。

友元

在c++中对象的私有属性可以借助友元来访问

友元的关键字为friend

实现方式:1.全局函数做友元

                   2.类做友元

                   3.成员函数做友元

#include<iostream>
#include<string>
using namespace std;

class building;

class gg
{
public:
    gg()
    {
        h = new building;
    }
    building* h;
    void vist();
};

class hh
{
public:
    building* g;
    hh()
    {
        g = new building;
    }
    void vist();
};

void goodfriend(building* a);

class building
{
    friend void gg::vist();  // gg 中的 vist 函数做友元
    friend class hh;          // hh 类做友元
    friend void goodfriend(building* a);  // goodfriend 函数做友元
public:
    building()
    {
        sittingroom = "客厅";
        bedroom = "卧室";
    }
    string sittingroom;
private:
    string bedroom;
};

void gg::vist()
{
    cout << h->bedroom;
}

void hh::vist()
{
    cout << g->bedroom << endl;
}

void goodfriend(building* a)
{
    cout << "好朋友正在访问:" << a->sittingroom << endl;
    cout << "好朋友正在访问:" << a->bedroom << endl;
}

这段内容比较简单,上面是运用的例子,这周的笔记就到这里。

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值