栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > C/C++/C#

C++ 【类和对象: 初始化列表,Static成员 --3】

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++ 【类和对象: 初始化列表,Static成员 --3】

目录

1.自定义类型中构造函数的问题

1.2 初始化列表

1.3 explicit关键字:禁止类型转换

总结:单参数的构造函数支持隐式类型转换

1.4 编译器优化

2.静态成员概念

3.内部类


1.自定义类型中构造函数的问题

由于构造函数的性质是:对内置类型不做处理,对自定义类型调用他的构造函数

当B没有写默认构造函数,提示报错:A”不具备相应的 默认构造函数,原因是A调用不到B的默认构造函数

class  B
{
public:
	B(int b)
	{
		_b = b;
	}
private:
	int _b;
};

class A
{
private: 
	int _a;
	B b;
};

当我们想用带参构造去A中构造对象B,再使用赋值传递给B,让B初始化时,编译器报错:B不存在默认构造函数

class  B
{
public:
	B(int b)
	{
		_b = b;
	}
private:
	int _b;
};

class A
{
public:
	A(int a, int b)
	{
		_a = a;
		//_b1._b = b;无法访问私有
		B b2(b);
		_b1 = b2;
	}
private: 
	int _a;
	B _b1;
};

int main()
{
	A a(1,1);
	return 0;
}

以上创建构造函数的方法是:函数体内初始化。但函数体内初始化会面临一些难以解决的问题,相比在函数体内赋值,提供了新方法来初始化:初始化列表


1.2 初始化列表

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

以下成员必须使用初始化列表初始化:

1.引用成员变量  和 const成员变量。原因在于:初始化只能初始化一次,他们必须在定义的时候初始化

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

注意事项:每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

自定义类型成员,必须在初始化列表初始化

要初始化_b1,只能通过初始化列表;或者写了默认构造函数,就可以不写初始化列表

class  B
{
public:
	B(int b)
	{
		_b = b;
	}
private:
	int _b;
};

class A
{
public:
	A(int a, int b)
		:_b1(b)//类型已经声明过:B _b1 ;初始化列表就像调用构造函数一样,只是前面没加B类型而已
	
	{
		_a = a;
		_b1._b = b;无法访问私有
		//B b2(b);
		//_b1 = b2;
	}
private: 
	int _a;
	B _b1;
};

int main()
{
	A a(1,1);
	return 0;
}

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

总结:初始化列表可以认为是成员变量定义的地方,每个成员都会在初始化列表初始化,不写初始化列表编译器也默认存在初始化列表。 统一的建议是内置类型也推荐使用初始化列表

传引用和const的写法,ref也是x和y的别名,修改ref也会修改y

class  B
{
public:
	B(int b)
	{
		_b = b;
	}
private:
	int _b;
};

class A
{
public:
	A(int a,int b, int& x)
		:_a(a)
		,_b1(b)
		,_ref(x)
		,_N(10)
	
	{
		_a = a;
		_ref++;
	}
private: 
	int _a;
	B _b1;
	int& _ref;
	const int _N;
};

int main()
{
	int y = 0;
	A a(2,1,y);

	return 0;
}

C++11中在声明打的补丁的缺省值,给的是初始化列表(初始化时没有显示给值就会用这个缺省值)

private: 
	int _a = 0;
	B _b1 = 0;

成员变量在类中是按照声明顺序来初始化,与其在初始化列表中的先后顺序无关(谁先声明先初始化)

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

输出结果是:1,随机值

原因在于先调用a2(a1),a1还没有初始化,赋随机值给a2,再初始化a1


1.3 explicit关键字:禁止类型转换

d1和d2的差别是什么? 虽然都是去调用构造函数初始化,但是过程不一样,原因在于:隐式类型转换

隐式类型转换中间都会产生临时变量,i并不是直接给d,而是有一个临时变量double接收i的值,再把临时变量值赋给d

int i =10;
double d = i;

对double 加引用不可以,因为d不是引用i而是引用临时变量,临时变量具有常性,不加const是权限放大,加上const编译成功

int i =10;
double& d = i;//错误
const double& d = i;

d1是直接调用构造函数

d2是 构造+拷贝构造+优化 ==直接调用构造函数

class Date
{
public:
	Date(int year)
		:_year(year)
	{
		cout << "Date(int year)" << endl;
	}
	Date(const Date& d)
	{
		cout << "const Date& d" << endl;
	}

private:
	int _year;
};

int main()
{
	Date d1(2022);
	Date d2 = 2022;
	return 0;
}

隐式类型转化的原因是Date有单参数的构造函数,d2拿2022构造一个Date的临时对象,再用临时对象拷贝构造d2,但是编译器默认会直接优化为构造

为了验证情况,explicit关键字可以修饰构造函数,禁止类型转换

d3并不是引用了2022,而是引用中间产生的临时变量

class Date
{
public:
	Date(int year)
		:_year(year)
	{
		cout << "Date(int year)" << endl;
	}
	Date(const Date& d)
	{
		cout << "const Date& d" << endl;
	}

private:
	int _year;
};

int main()
{
	Date d1(2022);
	//Date d2 = 2022;
	const Date& d3 = 2022;
	return 0;
}

总结:单参数的构造函数支持隐式类型转换

匿名对象

直接写类名,只能定义一次,生命周期只有一行,结束后立刻析构

使用场景:只需要调用对象中函数


1.4 编译器优化

编译器会对以下情况进行优化:

1.单参数的构造函数会进行隐式类型转换

2.f1(w1)传值传参会产生拷贝,是一次构造+一次拷贝构造

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)
{

}

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

 所以建议在传参的时候使用传引用和const,就没有拷贝构造

void f2(const W& w)
{

}

f1(W());  

f1函数需要W的对象,传一个匿名对象。本来应该是构造+拷贝构造,编译器优化成直接构造

void f1(W w)
{}

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

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

 f3的调用应该是构造+传值返回拷贝构造

W f3()
{
	W ret;
	return ret;
}

W w1 = f3()是一次构造,两次拷贝构造(ret拷贝做返回值,再拷贝给w1)

W f3()
{
	W ret;
	return ret;
}

int main()
{
	f3();//1构造,1拷贝
    cout << endl << endl;
	W w1 = f3();
	return 0;
}

结果却是一次构造,一次拷贝;其中有一个拷贝构造函数被优化,优化发生在ret临时对象拷贝返回值被优化,ret返回值直接给w1(在f3栈帧结束前,ret就把拷贝值给w1)

 

 如果分开写步骤w2,就变成了一构造,一拷贝,一赋值

W f3()
{
	W ret;
	return ret;
}

int main()
{
	f3();//1构造,1拷贝
    cout << endl << endl;
	W w1 = f3();//1构造,1拷贝

    W w2;
    w2 = f3();//一构造,一拷贝,一赋值
	return 0;
}

<<深度探索C++对象模型>>有讲这种情况

 

以下代码共调用多少次拷贝构造函数

Widget f(Widget u)

{  

  Widget v(u);

  Widget w=v;

  return w;

}

main(){

  Widget x;

  Widget y=f(f(x));

}

 

 

如果不发生优化:应该是1次构造,9次拷贝构造

在release版本下:是一次构造,5次拷贝构造(Widget w = v优化,编译器觉得w没有价值,可以直接用u做返回值)

 


2.静态成员概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;静态成员变量属于类也属于类中所有对象

用static修饰的成员函数,称之为静态成员函数。

静态成员特性

1. 静态成员属于整个类,也属于这个类的所有对象。不属于某个具体的对象,存放在静态区

2.静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明

3.类静态成员即可用 类名::静态成员 或者 对象.静态成员(并不在对象中找,.只是帮助突破类域,在类中找) 来访问

4.静态成员函数没有隐藏的this指针,不能访问任何非静态成员,只能访问静态成员

5.静态成员也是类的成员,受public、protected、private 访问限定符的限制

静态本质上是满足全局变量的封装问题;

想访问静态成员变量,不想通过对象去调用,而是指定类域调用,可以使用静态成员函数

特性说明

链接错误:只有声明。

普通成员定义在类定义时也定义出来,普通成员在初始化列表时初始化

class A
{
public:
	A() { ++_scount; }

	A(const A& t) { ++_scount; }
private:
	static int _scount;//声明
};

int main()
{
	A a1;
	A a2;
	return 0;
}

静态成员不能在初始化列表初始化,无法初始化静态成员;缺省值也给不了

静态成员必须在类外定义初始化

实现一个类,计算程序中创建出了多少个类对象。

有时候需要全局数据,但是使用全局变量十分危险(容易被修改,多文件使用麻烦等情况),这种情况下就需要用到静态成员(等同于创建专门给类使用的全局变量)

每个对象定义,要么构造要么拷贝构造,合计++即可统计

class A
{
public:
	A(){ ++_scount; }

	A(const A& t) { ++_scount; }
	
	static int GetCount()
	{
		return _scount;
	}

private:
	static int _scount;
};

int A::_scount = 0;

int main()
{
	A a1;
	A a2;
	cout << A::GetCount() << endl;
	return 0;
}

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

要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

利用构造函数的特性来完成累加

class Sum
{ 
public:
  Sum()
  {
     _ret +=_add;
     _add++; 
  }
    static int GetRet()
    {
        return _ret;
    }
    private:
    static int _ret;
    static int _add;
};

int Sum::_ret = 0;
int Sum::_add = 1;

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

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

如果不使用static,直接调用StackCreate,就产生了歧义

class StackOnly
{
	StackOnly()
	{}

public:
	static StackOnly StackCreate()
	{
		StackOnly s;
		return s;//使用传值返回,会再拷贝一份
	}
private:
	int _a = 1;
	int _b = 1;
};
int main()
{
	StackOnly s1 = StackOnly::StackCreate();//静态成员函数没有this指针,不需要使用对象去调用
	return 0;
}


3.内部类

把一个类定义到另一个类的内部。

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

内部类受外部类的类域限制,访问限定符

sizeof外部类A的大小是4,不算内部类B。可以想象B就是定义在外部,只是具有以上两个特性

class A
{
private:
	int _h;
public:
	class B
	{
	public:
	private:
		int _b;
	};
};

int main()
{
	cout << sizeof(A) << endl; // 4
	return 0;
}

转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/1037153.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号