C++入门之类和对象(下)

目录

一、再谈构造函数

1.构造函数体赋值

2.初始化列表(初始化成员)

3.explicit关键字

4.匿名对象

二、Static成员

1.概念

2.newcoder:求1+2+3+...+n_牛客题霸_牛客网

三、友元

1.友元函数

2.友元类

四、内部类

1.概念

六、拷贝对象时一些编译器优化

七、再次理解类和对象

总结


一、再谈构造函数

1.构造函数体赋值

在创建对象的时候,编译器通过调用构造函数,给对象中 各个成员变量一个合适的初始值。代码如下:

class Date
{
  public:
    Date(int year,int month,int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    
  private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1; //是一种定义 对对象整体的定义 而不是对成员的初始化
    ...
    const int i ; // 这种是错误的 因为const只初始化一次,必须在定义的时候同时初始化
}

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能称其为对象中成员变量的初始化,构造函数体中的语句只能称其为赋初值,而不能称为初始化。因为初始化只能初始化依次,而构造函数体内可以多次赋值。

还有如下情况:

int i = 1;
double d = i; //这里存在一个隐士的类型转换,产生了一个double的临时变量

//在c++中,对于类而言 如果要类型转换

A aa = 1; 

// 如果有隐式类型转换,则将int改为A 类型 产生一个临时变量 再以拷贝构造的方式给aa

// 但是编译器优化过 不用产生临时变量 直接进行类型转换


// 如果类中有一个const成员变量
   const int i ; //❌
   const int i = 0; //√
   const 变量只能初始化一次,所以必须要在定义的时候初始化

   C++中,在进入构造函数体内时候,引用变量和const变量都已经用不确定的值初始化好了,构造函数内能做的的只有赋值,而const类型和引用类型不可以赋值,所以需要在初始化列表中初始化

// A& ref = 10 ;  ×
// const A& ref = 10; √ 这里产生了临时对象 引用的时候不能优化 产生的临时变量具有常性   

2.初始化列表(初始化成员)

初始化列表:以一个冒号开始,接着以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。示例如下:

class Date
{
   public:
    Date(int year, int month, int day)
        :year(year)
        ,_month(month)
        ,_day(day)
    {}
    
    private:
    int _year;
    int _month;
    int _day;
};

注意:

1.每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

2.类中包含以下成员,必须在初始列表位置进行初始化:

  • 引用成员变量
  • const成员变量
  • 自定义类成员(且该类没有默认构造函数)

3.尽量使用初始化列表初始化,不管是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化

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

    private:
        int _hour;
};

class Date
{
    public:
        Date(int day)
        {
        }

    private:
        int _day;
        Time _t;
};

int main()
{
    Date d(1);
}

4.成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与在初始化列表中的先后次序无关

//三种必须用初始化列表初始化的情况

class A
{
    public:
        A(int a)
            :_a(a)
        {
        }
    
    private:
        int _a;
 };


class B
{
    public:
        B(int a, int ref)
        :_aobj(a)
        ,_ref(ref)
        ,n(10)
    {
    }

    private:
        A _aobj; //没有默认构造函数
        int & ref;
        const int _n;
};

3.explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值,其余均有默认值的构造函数,具有类型转换作用。

用explicit修饰构造函数,会禁止构造函数的隐式转换。

4.匿名对象

匿名对象的特点就是声明周期只有一行,可以在函数返回的时候使用,或者只需要一次性调用的时候可以使用匿名对象,不用专门创建一个对象去调用成员函数

class A
{
   public:
    A(int a = 0)
        :_a(a)
    {
        cout<<"A(int a)"<<endl;
     }
    
    ~A()
    {
       cout<<"~A()"endl;
    }
    
    private:
       int _a;
    };

    class Solution{
    public:
        int Sum_Solution(int n)
        {
            //...
            return n;
        }
};

    int main()
    {
        A aa1;
        // A aa1(); 不能这样定义对象,因为编译器无法识别这是一个函数声明还是对象定义
        
        //但是可以定义匿名对象 匿名对象的特点就是不用取名字
        // 但是它的生命周期只有一行,接下来就会自动调用析构函数
        A();
    }
class Sum
{
public:
	Sum()
	{
		_sum += _i;
		++_i;
	}

	static int GetSum()
	{
		return _sum;
	}
private:
	static int _i;
	static int _sum;
};

int Sum::_i = 1;
int Sum::_sum = 0;


class Solution {
public:
	int Sum_solution(int n)
	{
		Sum* ptr = new Sum[n];
		return Sum::GetSum();
	}

	~Solution()
	{
		cout << "已经析构" << endl;
	}
};

#include<iostream>
using namespace std;

int main()
{
	//专门创建了一个对象
	Solution s;
	cout << s.Sum_solution(10) << endl;


	//Solution s1(); // 不能这样定义 因为分不清楚是函数的定义还是声明
	Solution(); //匿名对象 没有名字
	//声明周期只有一行
	
	//只需要一次性调用 就可以用匿名对象 不需要专门创建一个对象去调用成员函数
	cout << Solution.Sum_Solution(10) << endl;
	return 0;
}




A fun(int n)
{
    int n;
    cin>>n;
    int ret = Solution().Sum_Solution(n);
    
    //构建一个名字为retA 的对象返回
    A retA(ret);  
    return retA;

    //直接使用一个匿名对象返回
    return A(ret);  
}

二、Static成员

1.概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称为静态成员变量,用static修饰的成员函数,称为静态成员函数。静态成员变量一定要在类外初始化。

//面试题:实现一个类,计算程序中创建了多少个对象

class A
{
    public:
        A()
        {
            ++count;
        }
        
        A(const A& t)
        {
            ++count;
        }

        ~A()
        {
            --count;
        }

     static int GetCount()
        {
            return count;
        }

    private:
        static int count;
};

int A::count = 0;

void TestA()
{
    cout<<A::GetCoun()<<endl;
    A a1,a2;
    cout<<A::GetCount()<<endl;
}

2.newcoder:求1+2+3+...+n_牛客题霸_牛客网

有一道题计算1+2+...N,这里就可以使用static 定义两个成员,i,sum,i表示加到n,sum表示总和,static一个成员函数,获取sum的值。这里在main中可以定义一个类数组,即调用n次构造函数,每次构造的时候都++sum,具体代码如下:

#include<climits>
class Sum
{
public:
    Sum()
    {
        _sum += _i;
        ++_i;
    }
    static int GetSum()
    {
        return _sum;
    }
private:
      static int _i;
      static int _sum;
};

 int Sum::_i = 1;
 int Sum::_sum = 0;

class Solution {
public:
    int Sum_Solution(int n)
     {
        Sum a[n];
        return Sum::GetSum();

    }
};
  • 静态成员为所有类对象共享,不属于某个具体的对象,存放在静态区
  • 静态变量必须在类外定义,定义的时候不添加static关键字,在类里只是声明
  • 类静态成员可用类名::静态成员或者对象.静态成员访问
  • 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  • 静态成员也是类的成员,受public,protected,private访问限定符的限制

三、友元

友元提供了一种突破封装的方式,有时候提供了遍历可以访问私有成员。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。友元分为友元函数和友元类

1.友元函数

友元函数可以直接访问类的私有成员,它是定义在类外的普通函数,不属于任何类,但是需要在类中加friend关键字声明。

比如:尝试去重载operator<<,因为cout的输出流对象和隐含的this指针抢占第一个参数的位置,必须cout在左操作数才能正常使用,所以需要用operator<<重载为全局函数,但是全局函数无法访问类中的私有成员,此时需要friend来解决。

class Date
{
    friend ostream & operaot<<(ostream& _cout, const Date &d);
    public:
        Date(int year,int month,int day)
        :_year(year)
        ,_month(month)
        ,_day(day)
    {}
    
    private:
        int _year;
        int _month;
        int _day;
};

ostream& operator>>(ostream& _cout, Date& _d)
{
    _cout<<d._year<<d._month<<_d._day<<endl;
    return _cout;
}

2.友元类

友元类的所有成员函数都可以是另外一个类的友元函数,都可以访问一个类中的非公有成员。


class Time
{
    friend class Date;  // 声明的日期类是时间类的友元类,则在日期类中可以直接访问time类的私有成员

    public:
        ///


    private:
        int _hour;
        int _min;
        int _s;
}

class Date
{
    public:
        Date()
        {
            ///
        }

        //日期类为时间类的友元类,在日期类里可以访问时间类的私有成员,但是时间类不能访问日期类的私有成员
        void TestTime(int hour,int min,int second)
        {
            _t._hout = hour;
            、、、
        }
    private:
        

}


  • 友元是单向的,不具有交换性
  • 友元关系不能传递
  • 友元关系不能继承

四、内部类

1.概念

如果一个类定义在另外一个类的内部,这个就称为内部类,内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象访问内部类的成员,外部类对内部类没有优越的访问权限。

内部类就是外部类的友元类,内部类可以通过外部类的对象参数访问外部类的所有成员,但是外部类不是内部类的友元。

1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
3. sizeof(外部类)=外部类,和内部类没有任何关系。

六、拷贝对象时一些编译器优化

可以参考一本书: 深度探索c++模型

在传参和传返回值的时候,一般编译器会做一些优化,减少对象的拷贝。

总结:

对象返回问题:

1.接收返回对象尽量拷贝构造接收,不要赋值接收

               // A aa1 = func() 拷贝构造接收

              // func()

                A aa2 = fun(); // 赋值接收

           2.函数中返回对象时,尽量返回匿名对象

               A func4()
               {
                    return A()   // 返回一个匿名对象 
                    // 构造 + 拷贝构造 优化为直接构造

                   // 所以可以直接返回匿名对象可以加速编译器的优化
                }

传参问题:

1.尽量使用 const & 引用传参

class A
{

};

// 传值传参
void func1(A aa)
{

}

//加const 可以接受普通对象也可以接受const对象 
void func2(const A& aa)
{
	//引用传参

}

//传值返回
A fun3()
{
    A aa;  // 构造
    return aa;   //拷贝构造    不会用aa作为返回值 因为aa出了作用域已经销毁 所以使用拷贝构造给一个变量
   
}


A func4()
{
    return A()   // 返回一个匿名对象  所以可以直接返回匿名对象可以加速编译器的优化
    // 构造 + 拷贝构造 优化为直接构造
}

int main()
{
	//传值传参
	A aa1 = 1; // 构造 + 拷贝构造 优化为直接构造   1为int 构造一个A类型的对象 拷贝构造给aa1 
	func1(aa1); // 传值传参 不能直接优化   构造aa1 +拷贝构造给形参

	// 有一个析构 aa形参的析构

	func1(2);   // 2构造一个A+拷贝构造 优化为直接构造  

	func1(A(3)); // 构造 + 拷贝构造 优化为直接构造

	func2(aa1);  //引用传参 不用构造和拷贝构造
	func2(2);   // 匿名对象 构造一个匿名对象  没有优化 结束直接析构
	func2(A(3)); //构造 引用不用使用拷贝构造

    fun3();  //不会优化

    A aa1 = fun3(); // 构造aa 拷贝构造aa给一个变量 再拷贝构造给aa1 编译器直接优化为一个构造+一个拷贝构造

    func4();   // 构造 + 拷贝构造  -- 优化为构造
    A aa3 = func4();  // 构造 + 拷贝构造 + 拷贝构造 --- 优化为构造
}

七、再次理解类和对象

现实生活中,实体计算机只认识二进制格式的数据,如果想让计算机认识现实生活的实体,用户必须通过某种面向对象的语言,对实体进行描述,通过编写程序,创建对象后计算机才可以认识。

比如想让计算机认识洗衣机,就需要:

1.用户先要对现实中洗衣机实体进行抽象---即在人为思想层面对洗衣机进行认识,洗衣机有什
么属性,有那些功能,即对洗衣机进行抽象认知的一个过程
2. 经过1之后,在人的头脑中已经对洗衣机有了一个清醒的认识,只不过此时计算机还不清
楚,想要让计算机识别人想象中的洗衣机,就需要人通过某种面相对象的语言(比如:C++、
Java、Python等)将洗衣机用类来进行描述,并输入到计算机中
3. 经过2之后,在计算机中就有了一个洗衣机类,但是洗衣机类只是站在计算机的角度对洗衣
机对象进行描述的,通过洗衣机类,可以实例化出一个个具体的洗衣机对象,此时计算机才
能洗衣机是什么东西。
4. 用户就可以借助计算机中洗衣机对象,来模拟现实中的洗衣机实体了。
在类和对象阶段,类是对某一类实体(对象)进行描述的,描述对象具有的属性,方法。描述完了
就形成一种新的自定义类型,采用该自定义类型实例化具体的对象。


总结

本章对构造函数,static成员,友元,内部类,匿名对象,编译器的优化拷贝对象进行简单总结,技术有限,如有错误请指正。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值