目录
1.自定义类型中构造函数的问题
1.2 初始化列表
1.3 explicit关键字:禁止类型转换
总结:单参数的构造函数支持隐式类型转换
1.4 编译器优化
2.静态成员概念
3.内部类
1.自定义类型中构造函数的问题由于构造函数的性质是:对内置类型不做处理,对自定义类型调用他的构造函数
当B没有写默认构造函数,提示报错:A”不具备相应的 默认构造函数,原因是A调用不到B的默认构造函数
classB
{
public:
B(int b)
{
_b = b;
}
private:
int _b;
};
class A
{
private:
int _a;
B b;
};
当我们想用带参构造去A中构造对象B,再使用赋值传递给B,让B初始化时,编译器报错:B不存在默认构造函数
classB
{
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,只能通过初始化列表;或者写了默认构造函数,就可以不写初始化列表
classB
{
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
classB
{
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;
}
【C++|C++ 【类和对象: 初始化列表,Static成员 --3】】
文章图片
静态成员不能在初始化列表初始化,无法初始化静态成员;缺省值也给不了
文章图片
静态成员必须在类外定义初始化
文章图片
实现一个类,计算程序中创建出了多少个类对象。有时候需要全局数据,但是使用全局变量十分危险(容易被修改,多文件使用麻烦等情况),这种情况下就需要用到静态成员(等同于创建专门给类使用的全局变量)
每个对象定义,要么构造要么拷贝构造,合计++即可统计
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;
}
推荐阅读
- C++|【C++】new/delete对象过程
- Windows核心编程|Windows提取环境变量
- c语言|学习了循环碰到了编写计算n的阶乘的题目,由此引发了一系列问题,包括一些初学者常见的坑,以及如何简化代码
- opencv|【OpenCV】 人脸识别
- 网络编程|TCP/IP网络编程(8) 基于Linux的多进程服务器
- c++|windows 下编译libcurl,通过url下载图片数据
- c++|C++通过url直接下载图片并保存
- 【学习笔记】
- std::cout 输出 unsigned char类型数据