7.类和对象(下)

一、再探构造函数

1)构造函数初始化还有一种方式:初始化列表(可以和函数体混用)
使用方式:冒号开始,逗号分割,每个成员变量后面跟一个放在括号中的初始值或表达式。

Date (int year,int month,int day)
:_year(year)
                
,_month(month)
,_day(day)
{}

2)每个成员变量只能在初始化列表,语法理解上 初始化列表是每个成员变量定义初始化的地方。
注: 这只是语法理解上,实际在创建对象时,成员变量就已经定义了,初始化列表只是被视作定义初始化的地方。
以下三种情况必须在初始化列表初始化:

1.引用类型
2.const成员变量
3.没有合适的默认构造函数可用的自定义类型

 这三种类型都必须要初始化,且只能在定义时初始化,因此只能用初始化列表初始化。

class Time
{
public:
	Time(int hour = 1)
		:_hour(hour)
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
};

class Date
{
public:
	Date()
		:_year(1)
		,_month(1)
		,_day(1)
	{}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

int main()
{
	Date d;
	return 0;
}

代码结果输出为:Time();

结论:没有显示写在初始化列表的自定义类型,编译器会调用其对应的默认构造函数来完成初始化。如果没有默认构造函数,编译报错。
3)C++11支持成员变量给缺省值,这个缺省值主要 给没有显式在初始化列表初始化 的成员变量用的。
4)尽量使用初始化列表初始化,因为不管是否显式实现,所有成员都会走初始化列表。
对于没有显式实现的 内置类型 成员变量是否初始化取决于编译器,C++没有规定。
对于没有显式实现的
自定义类型 成员变量会调用这个成员类型的 默认构造函数 ,如果没有默认构造函数则会编译报错。

总结:每个成员都要走初始化列表,每个构造函数都有初始化列表。
1.在初始化列表初始化的成员
2.没有在初始化列表初始化的成员
        a.声明的地方有缺省值用缺省值
        b.没有缺省值
                1)内置类型:未规定,随机值。
                2)自定义类型:调用默认构造,无默认构造,编译错误。

3.引用,const成员变量,没有默认构造函数的自定义类型,必须在初始化列表初始化,否则,编译错误。

5)初始化列表中按成员变量在类中声明的顺序(即成员变量在内存中存放的顺序)初始化,并不是按照初始化列表顺序。

class A
{
public:
	A(int a)
		:_a1(a)
		,_a2(_a1)
	{}
	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2 = 2;
	int _a1 = 2;
};

int main()
{
	A aa(1);
	aa.Print();
	return 0;
}

结果输出为:1 随机值
实际初始化顺序 _a2(_a1)   _a1(a)
编译不报错的原因是:_a1在创建对象时已经定义好了,不涉及语法错误。

注:实际上,成员变量已经在对象创建时就定义好了。
初始化列表只是在语法上定义初始化成员变量。

二、隐式类型转换

class A

{
public:
        A(int a = 0)

        :_a(a)

        {}

private:

        int _a;
}; 

以下例子表明了这种情况:

用途:

Stack st;

//第一种写法

A aa2(1);

st.Push(aa2);

//第二种写法

st.Push(1);

第二种写法,创建了A类型临时对象,用1构造。此时Push()函数用const A&参数接受这个临时对象。const可以维持临时对象的生命周期。
C++11支持多参数写法,例如:B b = {1,1};   st.Push({1,1});
注:使用explicit+构造函数,可以不让这种隐式类型转换发生。 

三、static成员

1)用static修饰的成员变量称为静态成员变量,一定要在类外初始化

private:
        static int a;
-----------------------------------------------
//类外初始化
int A::a = 10;

2)所有类对象共享,不属于某个具体的对象,不存在对象中,存放在静态区。 
3)用static修饰的成员函数称为静态成员函数,可以访问静态成员,不能访问非静态的,因为不存在this指针。
4)非静态的成员函数,可以访问静态和非静态的成员变量。
5)突破类域就可以访问静态成员,
1、类域::静态成员名
2、对象.静态成员名

求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)
解答一:友元类,使用静态对象不存在类中的性质,调用n次构造函数达到模拟累加

#include <linux/limits.h>
 
class A
{
public:
    A()
    {
        ++n;
        sum+=n;
    }
    friend class Solution;
private:
    static int n;
    static int sum;
};
 
int A::n = 0;
int A::sum = 0;
 
class Solution {
public:
    int Sum_Solution(int n) {
        A* p = new A[n];
        delete[] p;
        return p[0].sum;
    }
};

解答二:内部类,由于内部类默认是外部类的友元类,可以直接访问外部的私有成员 

#include <linux/limits.h>

class Solution {
public:
    class A
    {
    public:
        A()
        {
            ++n;
            sum+=n;
        }
    };
    int Sum_Solution(int n) {
        A* p = new A[n];
        delete [] p;
        return sum;
    }
private:        
    static int n;
    static int sum;
};
int Solution::n = 0;
int Solution::sum = 0;


用途:static可以用于记录现存的对象个数。

C c;
int main()

{

        A a;

        B b;

        static int d = 0;

        return 0;

}

下面是一个关于全局变量析构的示例,展示了当程序结束时全局变量的析构顺序,以及全局变量之间的依赖关系如何被正确处理。

#include <iostream>

class A {
public:
    A() { std::cout << "A constructed" << std::endl; }
    ~A() { std::cout << "A destructed" << std::endl; }
};

class B {
public:
    B(A& a) : a_ref(a) { std::cout << "B constructed" << std::endl; }
    ~B() { std::cout << "B destructed" << std::endl; }
private:
    A& a_ref; // 依赖于全局变量A
}

// 全局变量
A globalA; // 先构造
B globalB(globalA); // 后构造,依赖于globalA

int main() {
    std::cout << "Inside main" << std::endl;
    return 0; // main函数返回,开始析构全局变量
}

输出结果
当你运行这个程序时,输出将会是:
A constructed
B constructed
Inside main
B destructed
A destructed
解释
构造顺序:
globalA 在 globalB 之前构造,因为 globalA 是在 globalB 的构造函数中被引用的。
析构顺序:
当 main 函数返回时,首先析构 globalB,然后析构 globalA。这确保了在析构 globalB 时,globalA 仍然存在,从而避免了访问已销毁对象的问题。

同作用域的变量的析构顺序与它们的创建顺序相反,这是由C++的运行时环境所保证的,而不是由于栈的机制。这种析构顺序确保了程序在退出前能够正确释放所有全局变量所占用的资源。 

四、友元---------会增加耦合度,破坏了封装,不宜多用

1)友元提供了一种 突破类访问限定符 的方式。
友元分为:友元类 和 友元函数。
2)外部友元函数可以访问内部的私有和保护成员,仅仅只是声明,不是类的函数。
3)不受访问限定符的限制
4)一个函数/类可以是多个类的友元函数
5)单向性。A是B友元,B不是A友元。
6)不具备传递性。A是B友元,B是C友元,A不是C友元。
用途:类A,类B,其中类B想大量访问类A中的私有和保护的成员变量,此时可以把类B设置为类A的友元类。类B的所有成员函数都可以访问类A的私有和保护的成员变量。

五、内部类

1)一个类定义在另一个类的内部,叫做内部类。
2)内部类是一个独立的类(
不存在于外部类的中,即不占外部类的大小),只是受外部类访问限定符和类域的限制。
3)
内部类默认是外部类的友元
内部类的本质是一种封装,当A类和B类紧密相连,且A类设计出来只是为了给B类使用,这时就可以把A类设置成内部类。

六、匿名对象
目的:为了方便使用

//定义对象
A aa1;//调用默认构造

A aa1(); //有问题,因为编译器无法识别是创建对象还是函数声明,报警告。
A(); //匿名对象,生命周期只在当前行。

<algorithm>头文件,算法

int a[8] = {8,7,6,5,4,3,2,1};

sort(a,a+8); //默认升序

若要排成降序,两种解决方法:

1)加一个参数,函数指针
2)greater<int> gt; //比较器
        sort(a,a+8,gt);
        //或者
        sort(a,a+8,greater<int> ()); //此处使用了匿名对象

七、对象拷贝是的编译器优化。

1)现代编译器会为了尽可能提高程序的效率,在不影响正确性的情况下会尽可能减少一些传参或传参过程中的拷贝。

class A
{
public:
    A(int a)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }
    A(const A& p)
        :_a(p._a)
    {
        cout << "A(const A& p)" << endl;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
    void Print()
    {
        cout << "A::Print()->" << _a << endl;
    }
private:
    int _a;
};

隐式类型转换存在的优化 

A aa1 = 1;

结果:
A(int a);

~A();

按语法上,应为1构造了一个A类型的临时对象,临时对象再拷贝构造给aa1。
编译器优化为用1直接构造aa1。

传值传参存在的优化 

void f1(A aa)
{}
f1(A(1));

结果:
A(int a)
~A()
按语法,应为用1构造A类型的匿名对象,传值传参用这个匿名对象拷贝构造aa。
编译器优化为用1直接构造aa。
-------------------------------------------------------------------------------------------------------------------------
f1(1);
结果:
A(int a)
~A()
按语法,应为用1构造A类型的临时对象,临时对象拷贝构造给aa。
编译器优化为用1直接构造aa。

-------------------------------------------------------------------------------------------------------------------------

A aa1(1);

f1(aa1);
编译器激进也可能优化成直接构造。

传值返回存在的优化

A f2()
{
    A aa(1);
    return aa;
}
f2().Print();
cout << "**************" << endl;
结果:

A(int a)
A::Print()->1
~A()      ------临时对象生命周期只在当前行
**************
按语法,应为用1构造aa,aa拷贝构造给临时对象。
A(int a); //1构造aa

A(const A& p); //aa拷贝构造临时对象

~A();  //函数栈帧销毁,调用aa的析构

A::Print()->1 //临时对象调用打印函数

~A();  //临时对象生命周期只在当前行,调用临时对象析构

*************
优化为了用1直接构造了临时对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

挺6的还

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值