猿创征文|C++——类和对象4| 构造函数体赋值|初始化列表explicit关键字|匿名对象|static成员|静态成员变量|静态成员函数| static相关习题|友元

目录

再谈构造函数

 构造函数体赋值

 初始化列表

explicit关键字 

匿名对象 

static成员 

静态成员变量 

静态成员函数 

static相关习题 

 友元

内部类 

习题 下面代码共有多少拷贝构造 

习题 


再谈构造函数

 构造函数体赋值

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

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

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

 初始化列表

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

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成员变量
自定义类型成员(且该类没有默认构造函数时)

 

#include<iostream>
using namespace std;
class Time
{
public:
	Time(int hour)
	{
		_hour = hour;
	}
private:
	int _hour;
};
class Date
{
public:

	Date(int year, int month, int day)
	{
		Time a(day);
		_t = a;
		_year = year;
		_month = month;
		_day = day;
	}
private:
	Time _t;
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
	Date a(1,1,1);
	return 0;
}

当Time没有初始化列表,而我们相对其私有成员初始化时,可以用一个值进行接收,但是这样写程序执行后会报错,因为Time没有合适的默认构造函数

程序提供默认构造后可以执行

 那么Time程序要是一直没有默认构造,该如何修改呢?此时可以用初始化列表

 举例:自定义类型成员(且该类没有默认构造函数时)

#include<iostream>
using namespace std;
class Time
{
public:
	Time(int hour)
	{
		_hour = hour;
	}
private:
	int _hour;
};
class Date
{
public:

	Date(int year, int month, int day)
		:_t(day)
	{
		Time a(day);
		_t = a;
		_year = year;
		_month = month;
		_day = day;
	}
private:
	Time _t;
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
	Date a(1,1,1);
	return 0;
}

 用初始化列表,可以解决这一问题

俩种初始化方式的比较

第一种方式当程序走到左边这里的时候,再跳到Time的默认构造函数,这是因为程序没有找到用户写的Date默认构造函数,而是调用了系统默认生成的默认构造函数,对内置类型不处理,然后直接调用自定义类型的默认构造函数

所有类类型(class type)的成员都会在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中.

 当程序走到这里时,要对b调用默认构造函数

对于Time而言调用了俩次默认构造函数,一次是在调用Date默认构造函数时,还有一次是在对b调用默认构造函数,这种定义一个变量再赋值给另一个变量的写法比较麻烦 

如果用初始化列表写就不用这么麻烦,会直接调用初始化列表进行初始化

对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差别不是很大,但是对于类类型来说,最好使用初始化列表,为什么呢?由下面的测试可知,使用初始化列表少了一次调用默认构造函数的过程,这对于数据密集型的类来说,是非常高效的

结论:自定义类型成员,推荐用初始化列表初始化

初始化列表可以认为是成员变量定义的地方,在定义的地方会调用他的构造

这种在函数体内赋值,但是还是会走初始化列表(虽然啥都没写但是有初始化列表),然后在他定义的地方调用默认构造

这是日期类对象整体的定义,但是对象里面还有成员,C++通过初始化列表去认定成员的定义 

const成员变量也必须在初始化列表进行初始化

对const成员没初始化直接编不过,因为const修饰的对象在定义的时候就要初始化好,如果没有初始化,后面也不能对其赋值,而初始化列表就是定义的地方,所以要在初始化列表里面进行初始化,初始化列表使用逗号不用;

 引用成员变量也要在初始化列表初始化

我们直接在main函数里定义一个引用变量,执行程序会报错,因为定义的时候没有引用,

 

在类里面声明引用,执行程序,也报错

引用的定义在初始化列表,但我们在初始化列表没有对其进行初始化,所以会报错 

using namespace std;
class Time
{
public:
	Time(int hour)
	{
		_hour = hour;
	}
private:
	int _hour;
};
class Date
{
public:

	Date(int year, int month, int day,int x)
		:_t(day)
		, _N(100)
		,a(x)

	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	Time _t;
	int _year; // 年
	int _month; // 月
	int _day; // 日
	const int _N;
	int& a;
};
int main()
{
	int x = 0;
	Date a(1,1,1,x);
	return 0;
}

此时程序正常运行

x2是x1的一份临时拷贝,给引用重新赋值只能改变x2,不能改变x1 

如果想通过改变x2来改变x1,传参的时候再加一个引用即可

内置类型也推荐使用初始化列表,当然内置类型在函数体内初始化也没什么问题,但有些时候也需要用到函数体内初始化,初始化列表并不是万能的

观察下列程序运行结果

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();
}

 初始化列表的初始化顺序是按照声明的顺序来初始化的,这里先声明了a2,再声明了a1

explicit关键字 

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

d1和d2的写法一样, d1是直接调用构造,d2是隐式类型转换:构造+拷贝构造+优化=直接调用构造

d2用2022构造一个Date的临时对象,再对临时对象拷贝构造,但编译器一般都会优化这俩步

并没有打印拷贝构造,这是因为发生了优化 

加上关键字之后显示错误

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

所有的隐式类型转换中间都会产生一个临时变量,而这个临时变量具有常属性

去掉explicit关键字后如果这样写,引用的是中间产生的临时变量

 

红框的最终效果和蓝框的效果一样,但是红框比较麻烦,直接用隐式类型转换就行 

传参的时候红框要拷贝构造,蓝框直接调用构造,蓝框的直接构造含有隐式类型的转换

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

 

如果用引用传参,红框能过,蓝框过不了, 因为蓝框发生了隐式类型的转换,传过去的是具有常属性的拷贝值,而引用前面没加const说明引用对象权限大,常属性权限小,所以会报错

要解决这个需要加const

 第二种情况

2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具有类型转换作用
 

此时没用关键字explicit修饰

使用之后会报错,因为禁止了隐式类型转换 

匿名对象 

 

特点:生命周期只有这一行。

 只调用构造函数和析构函数,所在行结束后立即调用析构函数

匿名对象可以这样使用,直接调用函数

static成员 

静态成员变量 

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

实现一个类,计算程序中创建出了多少个类对象。
若用全局变量统计,则存在一个问题,就是全局变量谁都可以使用

用静态处理就能实现只能自己的类来使用

class A
{
public:
	A() { ++_scount; }
	A(const A& t) { ++_scount; }
	~A() { --_scount; }
	static int GetACount() { return _scount; }
private:
	static int _scount;//声明
};

只声明,没定义会报错

 用初始化列表初始化(定义)静态变量也会报错

 给缺省值也会报错,因为缺省值是给初始化列表的,该静态变量的定义没用在初始化列表,所以会报错

 静态变量只能在类外面定义初始化

此时程序正常运行 

特性
1. 静态成员为所有类对象所共享(属于这个类的所有对象),不属于某个具体的对象,存放在静态区
2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
对第一句话的解释,a1,a2,a3共享一个scount,scount属于这个类的所有对象,也属于整个类

 

 

最后一个为5是因为,前面的都打印完了,去执行最后一个程序,要调用一次构造函数,所以是5 

静态成员函数 

 静态成员变量,属于整个类,生命周期:整个程序运行期间,存在静态区

静态成员函数:没有this指针,不能去访问非静态成员,只能访问静态成员

static相关习题 

求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)

class A{
    public:
    A()
    {
        _sum+=_i;
        _i++;
}
   static int GetCount()
    {
       return _sum;
}
    private:
    static int _i;
    static int _sum;
};
int A::_i=1;
int A::_sum=0;
class Solution {
public:
    int Sum_Solution(int n) {
        A a[n];
        return A::GetCount();
    }
};

变长数组a[n]是为了让构造n次,

设计一个只能在栈上定义对象的类

 

我们创建的so1在栈上,so2在静态区

如果这样显示定义构造函数对于so1,变量定义在了栈上,对于so2,变量定义在了静态区,它们会在自己的区域去执行该构造函数

因此我们不公开构造函数,私有成员都在栈上,另创建一个函数来获取构造后的变量, 这样创建的变量就会在栈上

但是如果这样写会出现问题,要创建一个对象才能调用CreatObj,但是创建对象一执行程序就要构造,而构造函数又在CreatObj里面,就会产生鸡和蛋的问题

此时放在静态区即可

class StackOnly
{
public:
	static StackOnly CreatObj()
	{
		StackOnly so;
		return so;
	}
private:
	StackOnly(int x = 0,int y=0)
		:_x(x),
		_y(y)
	{}
private:
	int _x = 0;
	int _y = 0;
};
int main()
{
	StackOnly so=StackOnly::CreatObj();
	return 0;
}

此时可正常运行 

不能直接创建变量,而是要通过访问函数 

【问题】
1. 静态成员函数可以调用非静态成员函数吗?

不能,因为没有this
2. 非静态成员函数可以调用类的静态成员函数吗?
可以,静态属于整个类的所有对象

 友元

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

friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);

友元函数可访问类的私有和保护成员,但不是类的成员函数
友元函数不能用const修饰
友元函数可以在类定义的任何地方声明,不受类访问限定符限制
一个函数可以是多个类的友元函数


友元函数的调用与普通函数的调用原理相同

 友元能少用就少用,尽量用封装

友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Time类中直接访问Date
类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
友元关系不能传递
如果B是A的友元,C是B的友元,则不能说明C时A的友元。
友元关系不能继承,在继承位置再给大家详细介绍。
如这个俩个类,类友元之后,Time就能访问Date的私有和公共,Date不能访问Time

class Time
{
	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
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
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	void SetTimeOfDate(int hour, int minute, int second)
	{
		// 直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

内部类 

 概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中
的所有成员。但是外部类不是内部类的友元。
特性:
1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
3. sizeof(外部类)=外部类,和内部类没有任何关系。

class A
{
private:
	static int k;
	int h;
public:
	class B // B天生就是A的友元
	{
	public:
		void foo(const A& a)
		{
			cout << k << endl;//OK
			cout << a.h << endl;//OK
		}
	};
};
int A::k = 1;
int main()
{
	A::B b;
	b.foo(A());
	return 0;
}

A的大小还是它自身大小 

A里面没有B的成员,A的大小跟B没有任何关系,用B去定义成员之后A里面才会有B

 B定义在A的里面:

1.受A的类域限制,访问限定符(A和B同等地位,只不过是受到了A的限制)

 2.B天生是A的友元,A可以访问B的私有,B不能访问A

 3.A的静态成员在B里面可以直接访问,静态成员突破类域就能访问,这里不需要指定类域(不需要A::B),因为B是A的友元,当然也可写成A::B k.a

上面习题修改

class Solution {
    class A{
    public:
    A()
    {
        _sum+=_i;
        _i++;
}
  
};
public:
    int Sum_Solution(int n) {
        A a[n];
        return _sum;
    }
private:
    static int _i;
    static int _sum;
};
 int Solution::_i=1;
int  Solution::_sum=0;

这种传参方式不会调用拷贝构造

class W
{
public:
	W(int x = 0)
	{
		cout << "W()" << endl;
	}

	W(const W& w)
	{
		cout << "W(const W& w)" << endl;
	}

	W& operator=(const W& w)
	{
		cout << "W& operator=(const W& w)" << endl;
		return *this;
	}

	~W()
	{
		cout << "~W()" << endl;
	}
};

void f1(W w)
{

}

void f2(const W& w)
{

}

int main()
{
	f1(W());
	return 0;
}

本来是构造+拷贝构造——编译器进行了优化,优化成了直接构造

结论:一个表达式步骤中,连续的构造一般都会优化,合二为一

构造w2+再类型转换(拷贝构造)优化为直接构造 

但类类型做返回值类型时,不会优化

 这种情况又不一样

 本来应该时调用f3函数,给ret构造,然后return时,拷贝构造,赋值给w1时,再发生一次拷贝构造,也就是一次构造,俩次拷贝构造,但实际只发生了一次拷贝构造,是因为这里产生看优化一个表达式步骤中,连续的构造一般都会优化,合二为一,

 编译器省略掉了返回时的拷贝构造,直接把ret返回,但这种优化会出问题,

出了作用域对象以后,ret就销毁了,如果这里的内存被其它地方拿去使用,就成野指针了。

返回时临时拷贝的这个对象,如果比较小4-8字节就存在寄存器中,如果比较大就在上一层栈帧,这里的优化是让w1在f3结束前充当tmp价值(f3函数栈帧还没有结束,就把值已经返回了,不需要中间的拷贝)

 这俩种方式都一样

单独调用f3函数,没有把省略返回时候的拷贝值

习题 下面代码共有多少拷贝构造 

class Widget
{
public:
	Widget()
	{
		cout << "	Widget()" << endl;
	}
	Widget(const Widget& d)
	{
		cout << "Widget(const Widget& d)" << endl;
	}
	~Widget()
	{
		cout << "~Widget()" << endl;
	}
};
Widget f(Widget u)

{

	Widget v(u);

	Widget w = v;

	return w;

}
int main()
{

	Widget x;
	cout << endl;
	Widget y = f(x);

	return 0;
}

上面总共进行了1次构造,4次拷贝构造

 

本来是五次拷贝构造,发生了优化,把返回时候的拷贝构造给优化了 

一次构造,7次拷贝构造 

 本来应该是九次拷贝

这里优化了俩次return 时候创建临时对象的拷贝构造

如果调用Realse版本,结果又不一样

Realse版本下是5次 

打印观察,Realse版本优化了W w=v这一步

如果不在一个步骤里面,就不会发生优化

习题 

有一个类A,其数据成员如下: 则构造函数中,成员变量一定要通过初始化列表来初始化的是:( )

class A {

...

private:

   int a;

public:

   const int b;

   float* &c;

   static const char* d;

   static double* e;

};

A.a是不同数据成员,可以通过构造函数进行赋值

B.正确,常量以及引用只能通过初始化列表初始化

C.d,e是静态成员,只能在类外初始化

D.d是静态成员,只能在类外初始化

E.b常量只能通过初始化列表初始化,但不是最佳答案

F.c引用只能通过初始化列表初始化,但不是最佳答案

下面程序的运行结果是( )?

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 随机值

在一个cpp文件里面,定义了一个static类型的全局变量,下面一个正确的描述是:( )

A.只能在该cpp所在的编译模块中使用该变量

B.该变量的值是不可改变的

C.该变量不能在类的成员函数中引用

D.这种变量只能是基本类型(如int,char)不能是C++类型

A.正确,static限制了变量具有文件域

B.static变量是可以被改变的

C.可以被正常访问使用,以及通过成员来进行引用

D.静态变量也可以是自定义类型的变量

关于C++类中static 成员和对象成员的说法正确的是( )

A.static 成员变量在对象构造时生成

B.static 成员函数在对象成员函数中无法调用

C.static 成员函数没有this指针

D.static 成员函数不能访问static 成员变量

A.static成员变量在对象生成之前生成

B.普通成员函数是可以调用static函数的

C.static函数属于所有对象共享,不具备this指针

D.static函数唯一能够访问的就是static变量或者其他static函数

下面程序段包含4个函数,其中具有隐含this指针的是( )

int f1();

class T

{

  public:static int f2();

  private:friend int f3();

  protect:int f4();

};

A.全局函数不具备this指针

B.static函数不具备this指针

C.友元函数不具备this指针

D.正确,普通成员方法具有隐藏的this指针

一个类的友元函数能够访问类的( 所有成员)

下面有关友元函数与成员函数的区别,描述错误的是?( )

A.友元函数不是类的成员函数,和普通全局函数的调用没有区别

B.友元函数和类的成员函数都可以访问类的私有成员变量或者是成员函数

C.类的成员函数是属于类的,调用的时候是通过指针this调用的

D.友元函数是有关键字friend修饰,调用的时候也是通过指针this调用的

A.友元函数不是类的成员函数,就相当于你的朋友再亲密也不是你的家人,既然不是类成员函数,那和普通成员函数调用一样,不需要通过对象调用

B.友元的目的就是为了访问类的私有数据,成员函数可以直接访问类的私有数据

C.类的成员函数属于类,调用时其内部数据会通过this指针来调用

D.友元函数不具备this指针,更谈不上通过this调用,故错误

评论 61
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

头发没有代码多

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

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

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

打赏作者

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

抵扣说明:

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

余额充值