类和对象(3)

1.再谈构造函数

1.1构造函数体赋值

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

class Date
{
	public:
    	//函数体内赋值
    	Date(int year=0,int month=1,int day=1)
        {
            _year=year;
            _month=month;
            _day=day;
        }
private:
    int _year;
    int _month;
    int _day;
};

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

1.2初始化列表

1.2.1初始化列表的用法
  • 只有构造函数(构造函数和拷贝构造)才能使用。
  • 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。
  • 初始化列表是成员变量定义(给空间)的地方
class Date{
public: 
    ///初始化列表
    Date(int year=0,int month=1,int day=1)
 			:_year(year)
    		,_month(month)
             ,_day(day)
            {
                
            }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1(2020,1,12);
}
1.2.2初始化列表的意义

总结一下:

  1. 初始化列表——成员变量定义的地方
  2. const、引用,没有默认构造函数的自定义类型成员变量必须在初始化列表初始化,因为它们需要在定义的时候初始化
  3. 对于像其他类型的成员变量,如int _year,int _month,在哪初始化都可以。
const成员变量

const变量只能在定义的时候初始化。

class Date{
public: 
    ///初始化列表-成员变量定义的地方
    Date(int year=0,int month=1,int day=1)
 			:_year(year)
    		,_month(month)
             ,_day(day)
            {
                _N = 10 ;//error,修改了const值
            }
private://声明
    int _year; //声明
    int _month;//声明
    int _day;//声明
    const int _N;//需要在定义的初始化,而这里是声明,没有空间
};
int main()
{
    Date d1(2020,1,12);//对象定义/对象实例化
    
}

因此将const 成员变量放初始化列表中

class Date{
public: 
    ///初始化列表-成员变量定义的地方
    Date(int year=0,int month=1,int day=1)
 			:_year(year)
    		,_month(month)
             ,_day(day)
             ,_N(10)
            {
                
            }
private:
    int _year; //声明
    int _month;//声明
    int _day;//声明
    const int _N;//需要在定义的初始化,而这里是声明,没有空间
};
int main()
{
    Date d1(2020,1,12);//对象定义/对象实例化
    
}
引用变量

引用变量需要在定义的时候初始化。

class Date{
public: 
    ///初始化列表-成员变量定义的地方
    Date(int year=0,int month=1,int day=1,int i)
 			:_year(year)
    		,_month(month)
             ,_day(day)
			,_ref(i)
            {
                
            }
private:
    int _year; //声明
    int _month;//声明
    int _day;//声明
    int & ref;//声明
};
int main()
{
    int i = 0;
    Date d1(2020,1,12,i);//对象定义/对象实例化
    
}
没有默认构造函数的自定义类型成员变量

由默认的成员函数学习可得默认构造函数会自动调用自定义类的构造函数。

class A{
 public:
    A(int a)
    {
       _a = a; 
    }
  private:
    int _a;
}
class Date{
public: 
    ///初始化列表-成员变量定义的地方
    Date(int year=0,int month=1,int day=1)
 			:_year(year)
    		,_month(month)
             ,_day(day)
			,_aa(0)
            {
                
            }
private:
    int _year; //声明
    int _month;//声明
    int _day;//声明
    A _aa;//自定义类型对象声明
};
int main()
{
    Date d1(2020,1,12);//对象定义/对象实例化
    
}
1.2.3初始化列表的注意
1.2.3.1初始化列表和函数体内赋值并不是非此即彼的
///为什么要有初始化列表
class A
{
    public:
        A(int a)
    {
        _a=a;
    }
    private:
    int _a;
}
class B
{
    public:
    	//可以理解成初始化列表是对象的成员变量定义的地方
    	B(int a,int ref)
            :_aobj(1)
            ,_ref(ref)
            ,_n(10)
            {
                _x = 3;//不是非此即彼的
            }
     private:
   	  A _aobj; //没有默认构造函数的类
      int& ref; //引用和const必须在定义的时候就要初始化
      const int _n;
	  //成员变量的声明
      int _x;
}
int main()
{
    B b(1,2);//对象定义的时候
}
1.2.3.2默认的隐含初始化列表

虽然我们没有写初始化列表,但是初始化列表是成员变量定义的地方。__aa会在初始化列表定义的时候调用默认的构造函数初始化。(结合之前的学习默认构造函数的作用,如果没有就报错了;不然没有默认构造就必须传参调用显式初始化列表)

因此还有operator = 赋值。

以下代码会出现两次构造和一次赋值。

class A{
 public:
    A(int a = 0)
    {
		cout << "A (int a=0)"<<endl;
    }
    A(const A& a)
    {
        cout<<" A(int a =0 )"<<endl;
    }
    A& operator=(const A& a){
        cout<<" A& operator=(const A& a)"<<endl;
    }
  private:
    int _a;
}
class Date{
public: 
    ///初始化列表-成员变量定义的地方
    Date(int year=0,int month=1,int day=1,const A& a)
 			:_year(year)
    		,_month(month)
             ,_day(day)//隐含会调用自定义类型的默认构造函数
            {
                _aa = a;//这里发生的是赋值
            }
private:
    int _year; //声明
    int _month;//声明
    int _day;//声明
    A _aa;//自定义类型对象声明
};
int main()
{
    Date A;
    Date d1(2020,1,12,A);//对象定义/对象实例化
}

而如若使用初始化列表就只会出现两次构造,会提升效率。

这里也可以使用匿名对象

Date d1(2020,1,12,A());

因此:尽量使用初始化列表去初始化,因为对于自定义类型的成员变量,会自动调用初始化列表。

class Time
{
    public:
    	Time(int hour=0)//全缺省也是默认构造函数
        	:_hour(hour)
        {
			cout<<"Time()"<<endl;
        }
    private:
    	int _hour;
}
class Date
{
    public:
    	Date(int day)
        { }///会自动调用初始化列表,已经定义过了。我要是想初始化符合自己要求的的。
    
    	//函数体内赋值
    	Date(int day)
        {
        	Time t(1);
             _t=t;
	    }
    	//这样就方便很多
    	Date(int day)
           	:_t(1)
         {
                
         }
   private:
       int _day;
       Time _t;///自定义类型必须调用构造函数初始化
}
1.2.3.3成员变量的定义次序

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

因此平时自己写的时候按照声明次序进行初始化。

class A
{
    public:
    	A(int a)
        	:_a1(a)
             ,_a2(_a1)///先声明,还是个随机值
        {
            
        }
    void Print()
    {
		cout<<a1<<" "<<a2<<endl;
    }
    private:
    	int _a2;
    	int _a1;
}
int main()
{
    A aa(1);
    aa.Print();
}

1.3explicit关键字(禁止隐式类型转化)

两种类型转换都是会产生临时变量的。在学习引用的时候学习过。(反之试想,如果没有临时变量难道变原来变量的类型吗)

1.3.1隐式类型转换

C语言中以下代码是可以成立的。意义相似的类型可以互相转化。

int main()
{
    double d = 1.1;
    int i = d;
}

如何说明产生了临时变量

int main()
{
    double d = 1.1;
    const int & i = d;//需要引用
}
1.3.2强制类型转换

对于无关类型需要强制类型转换。

int main()
{
    int * p = &i;
    int j= (int )p;
}
1.3.3类的单参数隐式类型转换

本来用2021构造一个临时对象Date(2021),再用这个对象拷贝构造d2。但是结合之前的学习,C++编译器在连续的一个过程中,多个构造会被优化,合二为一。

会将这里的两个构造合二为一变成直接的构造了。

虽然两个都是直接构造的,但是过程是不一样的。

class Date
{
    public:
    	Date(int year)
            	:_year(year)
                {
         			cout<<"Date(int year)"<<endl;           
                }
    	Date(const Date& d)
        {
            cout<<"Date(const Date& d)"<<endl;
        }
    private:
    	int _year;
    	int _month;
    	int _day;
};
int main()
{
    //两者都调用了构造函数
    Data d1(2021);
    Data d2 = 2021; //临时对象Date(2022),再用这个对象拷贝构造d2.
}
1.3.4多参数的隐式类型转换–c++11
class Date
{
    public:
    	Date(int year,int month,int day)
            	:_year(year),
    			_month(month),
    			_day(day)
                {
         			cout<<"Date(int year)"<<endl;           
                }
    	Date(const Date& d)
        {
            cout<<"Date(const Date& d)"<<endl;
        }
    private:
    	int _year;
    	int _month;
    	int _day;
}
int main()
{
    Date d1(1,2,3);
    Date d2={1,2,3};//隐式类型转换,c++11才支持。
    //不想编译过去就加explicit
}
1.3.5禁止隐式类型转化

如果我们不想让隐式类型转换,就添加explicit—清楚明白的

class Date
{
    explicitpublic:
    	explicit Date(int year)
            	:_year(year)
                {
         			cout<<"Date(int year)"<<endl;           
                }
    	Date(const Date& d)
        {
            cout<<"Date(const Date& d)"<<endl;
        }
    private:
    	int _year;
    	int _month;
    	int _day;
};
int main()
{
    //两者都调用了构造函数
    Data d1(2021);
    Data d2 = 2021; //error
}
1.3.6类的隐式类型转化的应用

应用场景体现在string。我们构造的时候string="xxxx"

	string s1("hello");//构造
    string s2 ="hello";//隐式类型转换
//隐式转化的使用
    vector<string>v5;
    string s3("sort");//这样就很麻烦并且还要取名字
    v5.push_back(s3);
    
    //匿名对象
    v5.push_back(string("insert"));
    //推荐
    v5.push_back("erase");

2.static成员

2.1概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;

用static修饰的成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化。(因为该成员变量没有this指针)

  • 静态变量属于整个类,所有对象,生命周期在整个程序间运行
  • 在类成员函数中,可以随便访问
class A{
  public:
    A(int a =0 )
        :_a(a)
       { 
       		++_scount; 
       }
   
  private:
    int _a;
    static int _scount;//声明
};
int A::_scount=0;//定义

2.2特性

  1. static成员变量不存在对象中,存在静态区,属于这个类的所有对象,也是属于这个类
  2. static成员函数,没有this指针,不使用对象就可以调用–>fun::

静态成员函数可以调用非静态成员函数(/成员)吗?不行。没有this指针

非静态成员函数可以调用类的静态成员函数吗?可以

2.3访问方式

类内部随便访问。

2.3.1类外面访问成员变量

对象和指定域名的方式都可以访问,前者是突破类域。

2.3.1.1公有的成员变量——直接访问
class A{
  public:
    A(int a =0 )
        :_a(a)
       { 
       		++_scount; 
       }
  public:
    static int _scount;//声明
  private:
    int _a;
};
int A::_scount=0;//定义

int main()
{
    A a1;
    A a2 = 1;
    cout<< A::_scount<<endl;
    cout<< a1._scount<<endl;
    cout<< a2._scount<<endl;
}
2.3.1.2成员函数

成员函数可以访问静态成员

class A{
  public:
    A(int a =0 )
        :_a(a)
       { 
       		++_scount; 
       }
    int GetCount(){//静态成员函数没有this指针,只能访问静态成员变量和成员函数
        return _scount;
    }
  private:
    int _a;
    static int _scount;//声明
};
int A::_scount=0;//定义

int main()
{
    A a1;
    A a2 = 1;
    cout<<a1.GetCount()<<endl;
    cout<<a2.GetCount()<<endl;//帮助突破类域
}
2.3.1.3静态成员函数

静态成员函数没有this指针,只能访问静态成员变量和成员函数

class A{
  public:
    A(int a =0 )
        :_a(a)
       { 
       		++_scount; 
       }
    static int GetCount(){//静态成员函数没有this指针,只能访问静态成员变量和成员函数
        return _scount;
    }
  private:
    int _a;
    static int _scount;//声明
};
int A::_scount=0;//定义

int main()
{
    A a1;
    A a2 = 1;
    cout<<A::GetCount()<<endl;
    cout<<a1.GetCount()<<endl;//帮助突破类域
}
2.3.2类的非静态成员函数和静态成员函数互相调用

静态成员函数可以调用非静态成员函数(/成员)吗?不行。没有this指针

非静态成员函数可以调用类的静态成员函数吗?可以

class Date
{
    public:
    	Date(int year=0,int month=1,int day=1)
        {
        	    
        }
    	void f1()
        {
	
        }
    	static void f2()
        {
		   f1();//没有this指针
        }
    private:
    	
}
class Date{
public:
    	void f3()
        {
            f4();//突破类域+访问限定符就可以访问 Date::f4();/对象.f4()
            //类里面是一个整体都这域中,类里面不受访问限定符限制
        }
    	static void f4()
        {
            
        }
private:
    
};

2.3面试题

设计出一个类A,可以计算这个类总计产生了多少对象?

  • 产生类的过程只在构造函数和拷贝构造函数中
int n=0;
class A
{
    public:
    	A()
        {
			++n;
        }
    	A(const& D)
        {
            ++n;
        }
    	int GetN()
        {
            return n;
        }
    	int& GetN()
        {
            return n;
        }
    	static int GetN()///注意!!static函数没有this指针,所以函数中不能访问非静态的成员
        {
            return n;
        }
    private:
    	static int n;//声明 不是属于某个对象,是属于类的所有对象,属于这个类
    //n不在对象中,n在静态区中。
}
int A::n=0;//反过来想,不可能在构造函数初始化。那样每个对象都要初始化了。
A f1(A a)//拷贝构造
{
    return a;//传值返回
}
int main()
{
    A a1,a2;
    f1(a1);
    //n=1;//代码的问题是谁都可以对n进行修改。缺失了封装。能不能犯错和容不容易犯错的问题。
    f2(a2);
    //一共产生了6个对象
    a1.GetN();
    a2.GetN();
    
    a1.GetN()=10;///error :int 返回的是临时变量(有常性)
    a2.GetN()=10;//ok:对于int& 可以。
    A::GetN();
}

https://www.nowcoder.com/questionTerminal/7a0da8fc483247ff8800059e12d7caf1

  • 类 A[maxn] —>调用maxn次构造函数

3.c++11的成员初始化——声明时给缺省

3.1声明时给缺省的概念

C++11支持非静态成员变量在声明时进行初始化赋值,但是要注意这里不是初始化,这里是给声明的成员变量缺省值。

静态的不能这样给缺省值,必须在类外面定义。

对于类的默认构造函数没有处理内置类型,以下代码输出的是随机值。

class B
{
 public:
    B(int b = 2)
    :_b(b)
    { }
 private:
    int _b;
};
class A
{
public:
    int _a1;
    B _bb;
};
int main()
{
    A aa;
    cout<<aa._a1<<endl;
}

cpp11的补丁——声明给缺省。要注意这里是声明,不是初始化,这里是给成员变量缺省值

3.2声明时给缺省的实例

下份代码跑出的就不是随机值。

class B
{
 public:
    B(int b = 2)
    :_b(b)
    { }
 public:
    int _b;
};
class A
{
public:
    int _a1=1;
    B _bb;
};
int main()
{
    A aa;
    cout<<aa._a1<<endl;
	cout<<aa._bb._b<<endl;
}

image-20220119185214506

class B
{
 public:
    B(int b = 2)
    :_b(b)
    { }
 private:
    int _b;
};
class A
{
    A()//如果在初始化列表阶段没有对成员变量初始化,它就会使用缺省值初始化
    {}
private:
    int _a1=1;
    B _bb;
};
int main()
{
    A aa;
    cout<<aa._a1<<endl;
    cout<<aa._bb._b<<endl;
}

image-20220119185305497

class B
{
 public:
    B(int b = 0)
    :_b(b)
    { }
 private:
    int _b;
};
class A
{
    A()//如果在初始化列表阶段没有对成员变量初始化,它就会使用缺省值初始化
    {}
private:
    int _a1=0;
    B _bb1=10;
    B _bb2=B(20);
    int *p = (int *)malloc(4*10);
    int arr[10] = {1,2,3,4,5};
};
int main()
{
    A aa;
    cout<<aa._a1<<endl;
}

静态的不能这样给缺省值,必须在类外面定义。

4.友元

友元分为:友元函数友元类

友元提供了一种突破封装的方式,有时候提供了便利,但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

4.1友元函数

  • 某种场景需要类外面访问类里面私有或者保护的方式。
    • cout
      • cout是ostream的对象
      • 内置类型编译器实现了
  • 友元函数可以访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰(函数名+const修饰的是this指针指向内容,这里没有this指针,友元函数参数列表有两个嘛)
  • 友元可以在类定义的任何地方声明,不受类访问限定符限制,写在任意位置都可以。
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用和普通函数的调用一样

双操作数的运算符重载的时候,规定第一个操作数是左操作数,第二个参数是右操作数。

  • 由this指针的理解和运算符重载可以知道,运算符重载里面,双操作数左操作数一定是对象,第二个参数右操作数。
class Data{
    public:
    	void operator<<(ostream& os){
            return os<<_year<<" "<<_month<<" "<<_day;
        }
    private:
    	int _year;
    	int _month;
    	int _day;
};
int main()
{
      Data d1;
      d1.operator<<(cout);//ok 第一个操作数是左操作数,第二个操作数是右操作数。this指针的位置是规定好的。
      d1<<cout;
}
  • 一种方式是使用全局函数,为了保护封装性就要使用获得变量的函数或者改成public成员变量
class Data{
  	public:
    
    private:
    	int _year;
    	int _month;
    	int _day;
};
//写到类外面就没有this指针了
void operator<<(ostream& out ,const Date&d)
{
    out<<d.year<<"/"<<d.month<<"/"<<d.day;///但是此时无法访问私有的,所以要友元。
}
//连续输出
ostream& operator<<(ostream& out,const Date& d)
{
    out<<d.year<<"/"<<d.month<<"/"<<d.day;
    return out;
}
int main()
{
    cout<<d1<<endl;
    cout<<d1<<d2<<endl;
    //cout<<d1的返回值为out
    //operator<<(out,d2)
}
  • 还有一种方式就是友元,友元必须要在类内声明,表示是这个类的友元。
class Date
{
    public:
    friend void f(Date& d);///友元函数
    friend istream& operator>>(istream& in,Date& d);
    friend void operator<<(ostream& out,const Date& d);//几个参数就几个,cout对象+*this
    //第一个参数是this,this是d1的
    private:
    	int _year;
    	int _month;
    	int _day;
}
//cout-->ostream
//cin-->istream
istream& operator>>(istream& in,Date& d)
{
    in>>d._year>>d._month>>d._day;
    return in;
}
int main()
{
    Date d1;
    int i=0;
    cout<<d1<<d2<<i<<endl;
	int x=1;
    double y=1.11;
    //为什么能自动识别类型,因为函数重载了,匹配了不同的函数
    cout<<x; //cout.operator(&cout,x);
    cout<<y; //cout.operator(&cout,y);
}

4.2友元类

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

4.2.1友元类的特性

有点像”关注"。

  • 友元关系是单向的,不具有交换性。
  • 比如Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
  • 友元关系不能传递如果B是A的友元,C是B的友元,则不能说明C时A的友元。
4.2.2友元类的实例
class Time
{
    friend class Date;
    public:
    	Time(int hour=0,int minute=0,int second=0)
            :_hour(hour)
            ,_minute(minute)
            ,_second(second)
            {}
    private:
    	int _hour;
    	int _minute;
    	int _second;
};
class Date
{
    Date(int year=0,int month=1,int day=1)
        {
        	    
        }
	
     void SetTimeOfDate(int hour,int minute,int second)
         {
             //直接访问时间类的私有成员
             _t.hour=hour;
             _t.minute=minute;
             _t.second=seocnd;
         }

   private:
        int _year;
    	int _month;
    	int _day;
    	Time _t;
};
int main()
{
    
}

5.内部类

5.1概念

用的很少,Java用的比较多。

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。

外部类对内部类没有任何优越的访问权限。

注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

5.2特性

把一个类定义到另一个类的里面。

  • 内部类天生是另一个类的友元
  • 注意内部类的定义方法

内部类:

  1. 内部类B和在全局定义基本一样,只是他受外部类A类域的限制,定义在A的类域中
  2. 内部类B天生就是A外部类的友元,也就是B中可以访问A的私有或保护,但是A不能访问B的私有或保护(内部的可以访问外部的)[类似外部类是内部类的舔狗]

5.3实例

请问sizeof(A)是多少?答案是4。注意A类型的对象中没有B,B类型中的对象没有A。

class A
{
    private:
    	static int k;
    	int h;
    public:
    	//内部类
    	class B ///B天生就是A的友元
        {
            //friend class A;
            public:
            	void foo(const A& a)
                {
                    cout<<k<<endl;//ok
                    cout<<a.h<<endl;//ok
                }
           	private:
            	int _b;
            public:
            	int _bb;
        };
};
int A::k=1;
int main()
{
    A aa;
    ///定义B
    b.foo(A());
    cout<<sizeof(A)<<endl;
    cout<<A::B _bb<<endl;
    return 0;
}

6.匿名对象

  • 命名对象的生命周期在当前函数
  • 匿名对象的生命周期只在这一行

只有我这一行会使用这个创建对象,别人不需要使用

#include<iostream>
using namespace std;
class Solution
{
    Solution()
    {
		cout<<"Solution"<<endl;
    }
    int Sum_Solution(int n)
    {
        //..
        return n;
    }
    ~Solution()
    {
        cout<<"~Solution()"<<endl;
    }
}
int main()
{
 	Solution s1;//s1的生命周期在main函数中
    s1.Sum_Solution(10);
    //Solution();///匿名对象 ,生命周期就在这一行
    Solution().Sum_Solution(10);///只有我这一行会使用这个创建对象,别人不需要使用
}

7.再次理解封装

面向对象三大特性:封装、继承、多态。

为什么要封装:

  • 现实世界里面本身就存在各种各样的封装
  • 面向对象更好地去模拟描述这个世界
  • 而实际工程项目写代码,本质就是模拟现实世界地运转
  • 面向对象更关注类和类之间的关系
  • 工程中要提倡低耦合,高内聚。工程项目才好维护扩展。
    • 低耦合—类和类之间的关系越少越好,一个地方出问题了就不会都出问题
    • 高内聚—想给你访问的给你访问,不给你访问就不能访问,要管的都是自身
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值