深圳网站制作的公司有哪些,合肥市建设局网站,建网站报价 优帮云,外贸人常用网站目录 一、构造函数补充
1、初始化列表
1.1、初始化列表概念
1.2、初始化列表性质
2、explicit关键字
二、static成员
1、概念及使用
2、性质总结
三、友元
1、友元函数
2、友元类
四、内部类
五、拷贝对象时的一些编译器优化 一、构造函数补充 在《类和对象#x…目录 一、构造函数补充
1、初始化列表
1.1、初始化列表概念
1.2、初始化列表性质
2、explicit关键字
二、static成员
1、概念及使用
2、性质总结
三、友元
1、友元函数
2、友元类
四、内部类
五、拷贝对象时的一些编译器优化 一、构造函数补充 在《类和对象二》中我们已经学习了关于构造函数的大部分内容然而这些内容还不足以解决全部的问题。
例如当我们写下如下代码时运行程序
class Test
{
public:private:int _a;int _b;
};int main()
{Test t;return 0;
}
程序运行成功 但是我们再在成员变量中增加一个 const 类型变量时 程序报错了这是因为我们在定义 const 类型变量时必须要进行初始化。但是由于默认生成的构造函数对于内置类型不做处理也就无法对 const 类型变量进行初始化因此而报错。 虽然这里的问题可以通过 C11 新增的针对内置类型成员打的补丁来解决即内置类型成员变量在类中声明时可以给默认值。但是在 C11 之前这个问题是如何解决的呢 为了解决这个问题我们引入一个新的概念初始化列表。
1、初始化列表
1.1、初始化列表概念 初始化列表以一个冒号开始接着是一个以逗号分隔的数据成员列表每个 成员变量 后面跟 一个放在括号中的初始值或表达式。
形如
class Test
{
public:Test():_a(1) //初始化列表, _b(2), _c(3){//构造函数的函数体}
private:int _a;int _b;const int _c;
};int main()
{Test t;return 0;
} 初始化列表是成员变量定义的地方。不管我们写没写初始化列表编译器都会自己过一遍初始化列表。编译器会把我们在初始化列表中写了的成员变量按照我们写的来进行初始化把我们没在初始化列表中写的成员变量初始化成默认值。 对于普通的内置类型成员变量是可以初始化成默认值的因为之后还可以随意修改。但是对于 const 类型的成员变量因为无法再修改所以必须要被初始化为一个有意义的初始值。
与之同理的还有引用类型的成员变量 除了以上两类成员变量还有一类成员变量也必须要放在初始化列表位置进行初始化那就是自定义类型成员(且该类没有默认构造函数时)。 因为该类没有默认构造函数所以在初始化时必须要传参否则会报错。这里可以结合《类和对象二》中默认构造函数对于自定义类型成员变量会调用它的默认构造函数来理解原因就是编译器会自动走一遍初始化列表遇到自定义类型成员变量会做出对应的处理。
总结类中包含以下成员必须放在初始化列表位置进行初始化
引用成员变量const成员变量自定义类型成员(且该类没有默认构造函数时)对于其他类型的成员变量则就算不在初始化列表中写出来也不会报错。
1.2、初始化列表性质 我们推荐尽量使用初始化列表进行初始化。因为不管你是否使用初始化列表编译器对于所有成员变量都一定会走一遍初始化列表并使用初始化列表进行初始化。 每个成员变量在初始化列表中只能出现一次初始化只能初始化一次。
我们来看如下代码
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();
}
大家可以看出最终的结果是什么吗 运行程序后发现 _a1 被初始化为了 1 但是 _a2 却被初始化为了随机值。这是为什么呢 原因是在初始化列表中变量初始化的顺序并不是看在初始化列表中排列的顺序而是看在类中声明的顺序。因为在类中是先声明的 _a2 所以在变量初始化时就先初始化了 _a2 。又因为在初始化列表中_a2 是使用 _a1 来初始化的而此时 _a1 尚且还是一个随机值所以就导致 _d2 被初始化成了随机值。
2、explicit关键字
我们来看如下代码
class A
{
public:A(int a):_a1(a){}void Print(){cout _a1 endl;}
private:int _a1;
};int main()
{A aa1(1);A aa2 1;aa1.Print();aa2.Print();
}
在编译器中运行 可以发现对象 aa1 、 aa2 的成员变量都被初始化为了 1 。 这是不是说明这两种写法的意义是一样的呢其实不是的第一种写法是调用了构造函数来初始化的。而第二种写法实际上是一个隐式类型转换把整型数字 1 进行类型转换转换成类类型存储到类类型临时变量中再把该临时变量赋值给 aa2 。 写一行代码来证明一下 A ref 10; 代码报错显示 int 无法转换为 A 类型这是因为临时变量具有常性不可更改所以我们要把 ref 改为 const 类型 const A ref 10; 程序运行成功。 如果我们不希望发生这种隐式类型转换则可以使用关键字 explicit 。 此时第二种写法就已经不被允许了。用 explicit 修饰构造函数将会禁止构造函数的隐式转换。 补充说明 类型转换针对的是单参数构造函数C98 不支持多参数构造函数。但是在C11 中对此进行了拓展使多参数构造函数也支持隐式类型装换了。形如 A aa3 {2, 2}; 二、static成员
1、概念及使用 声明为 static 的类成员称为类的静态成员。用 static 修饰的成员变量称之为静态成员变量。用 static 修饰的成员函数称之为静态成员函数。静态成员变量一定要在类外进行初始化。 静态成员不属于某个对象而是属于所有对象属于整个类。 例如我们实现了一个类现在想要计算程序中创建出了多少个类对象就可以使用静态成员变量来计算
class A
{
public:A(int a 0){count;}A(const A aa){count;}//读取私有的成员变量 count int GetCount(){return count;}
private:static int count; //声明
};int A::count 0; //定义初始化int main()
{A aa1;A aa2(aa1);return 0;
} 静态变量是被存放在静态区的被所有类对象共用。又因为静态变量 count 是在类域中声明的所以变量名也不会与外界的变量名相互冲突。 需要注意的是静态变量的初始化不能在类内进行只能放在类外。这是因为 count 作为静态变量被所有对象共用不应该在初始化列表中被初始化在初始化列表中进行初始化的变量是单独属于某个对象的。
所以静态变量的声明放在类内而定义是放在全局的。在全局定义的时候要加上 域名: : 。 因为我们实例化了两个对象 aa1 、aa2 所以 count 的值为 2 所有对象都共用一个 count 。 因为静态变量 count 是存放在静态区的而不是对象内所以蓝色方框框起的 - 符号没有访问到对象内部的数据只起到了提示域名的作用不属于解引用。具体相关知识可以参考《类和对象一》。 但是如果当前函数作用域内没有对象的话使用起来就会有些麻烦像下面这样 由于 main 函数中没有对象也就无法通过对象调用 GetCount 函数来读取 count 的值。于是只能专门实例化出一个对象来读取同时还要把读取到的 count 减去一去掉这个我们新定义出来的没有其他实际意义的对象。 补充内容因为我们实例化出对象 aa 仅仅只是为了在这个地方使用一次一次过后就不会再去使用它。所以这里可以使用一个特殊的对象匿名对象来简化代码。
在《类和对象二》中我提到过在实例化对象时不可以写成这种形式
A aa();
因为编译器无法区分这段代码是一个函数的声明还是调用默认构造函数。但是下面这种写法是可以的
A();
意为实例化了一个匿名对象他的特点是生命周期只存在于这一行刚好符合我们只调用一次的需求所以读取 count 的值时我们也可以这样写 但是这样写起来的话还是太过于麻烦也不够优雅。为了解决这个问题我们再来学习一个东西静态成员函数。
静态成员函数是在成员函数前面使用 static 修饰。他没有 this 指针于是我们可以直接调用该函数 同时由于静态成员函数没有 this 指针也就没有办法访问非静态成员。可以说静态成员函数就是为了静态成员变量而生的。 有了上面的知识我们来看一下下面这段代码创建了多少了对象
void func()
{A aa1;A aa2(aa1);A aa3[10];
}
int main()
{func();cout A::GetCount() endl; return 0;
} 答案是 12 个因为 aa3[10] 是一个容量为 10 的自定义类型数组也就调用了 10 次构造函数。
2、性质总结
静态成员为所有类对象所共享不属于某个具体的对象存放在静态区静态成员变量必须在类外定义定义时不添加static关键字类中只是声明类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问静态成员函数没有隐藏的this指针不能访问任何非静态成员静态成员也是类的成员受public、protected、private 访问限定符的限制
三、友元 友元提供了一种突破封装的方式有时提供了便利。但是友元会增加耦合度破坏了封装所以 友元不宜多用。友元分为友元函数和友元类。
1、友元函数 问题现在尝试去重载 operator 然后发现没办法将 operator 重载成成员函数。因为 cout 的输出流对象和隐含的 this 指针在抢占第一个参数的位置。 this 指针默认是第一个参数也就是左操作数了。但是实际使用中 cout 需要是第一个形参对象才能正常使用。所以要将 operator 重载成全局函数。但又会导致类外没办法访问成员此时就需要友元来解决。 operator 同理。 友元函数可以直接访问类的私有成员它是定义在类外部的普通函数不属于任何类但需要在 类的内部声明声明时需要加 friend 关键字。 关于友元函数的说明
友元函数可访问类的私有和保护成员但不是类的成员函数友元函数不能用const修饰友元函数可以在类定义的任何地方声明不受类访问限定符限制一个函数可以是多个类的友元函数友元函数的调用与普通函数的调用原理相同
2、友元类 除了函数可以是类的友元之外类也可以是类的友元。友元类的所有成员函数都可以是另一个类的友元函数都可以访问另一个类中的非公有成员。 关于友元类的说明
友元关系是单向的不具有交换性。 比如上述Time类和Date类在Time类中声明Date类为其友元类那么可以在Date类中直接访问Time类的私有成员变量但想在Time类中访问Date类中私有的成员变量则不行。友元关系不能传递 如果C是B的友元 B是A的友元则不能说明C时A的友元。友元关系不能继承在继承位置再给大家详细介绍
四、内部类 内部类的概念如果一个类定义在另一个类的内部这个内部的类就叫做内部类。 我们写一个内部类来观察一下 B类定义在A类内部但是A类对象 aa 的大小为 4 个字节只占了一个整型的空间。这是因为A类里面只有 a 而没有 b 。 其实内部类仅仅只是定义在了另一个类的里面而已和定义在全局并没有什么区别只是内部类受到了外面这个类的类域的限制。
如果想要使用内部类则需要在前面说明内部类的域
A::B bb;
如果内部类是外部类的私有类型则无法直接使用内部类 需要注意的是内部类是外部类的友元类内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元类。
五、拷贝对象时的一些编译器优化
首先是我们上面介绍过的隐式类型转换 按照正常的逻辑编译器会首先调用构造函数来创建一个类类型的临时变量然后再调用拷贝构造函数使 aa1 变为类类型临时变量的拷贝。为了简化过程编译器把拷贝构造 构造优化为了直接构造。
需要注意的是这种优化只能存在于同一表达式构造完成之后直接把获得的临时变量用于拷贝构造的情况。
适用这种情况的除了赋值、连续赋值之外还有一些其他的情况如传值传参 这两行代码中传值传参时所进行的构造 拷贝构造被优化为直接构造。
传引用传参则因为无需拷贝而无需优化。
当拷贝构造用于返回值时例如以下场景
class A
{
public:A(int a 1):_a(a){}
private:int _a;
};A func()
{A aa;return aa;
}int main()
{func();A aa1 func();return 0;
}
按照正常逻辑这里共需要一次构造两次拷贝构造 而编译器进行优化时会把这个过程优化为一个构造加一个拷贝构造去除同一表达式中的冗余部分。
如果我们直接返回匿名对象时例如 编译器同样会把同一表达式中多余的步骤优化掉。 从以上各种例子中我们可以知道在传参和传返回值的过程中一般编译器会做一些优化减少对象的拷贝这个在一些场景下还是非常有用的。 同学们需要注意的是这里有一个容易弄混的地方 这两种方式是不同的第一个方框框起的代码属于拷贝构造而第二个方框框起的属于赋值重载。编译器可以优化第一种而没有办法优化第二种。
了解了以上知识我们日后写代码时就可以有意识的遵守三点规则
接收返回值对象尽量用拷贝构造方式接收而不要赋值重载方式接收。函数中返回对象尽量返回匿名对象。传参时尽量使用传引用传参。关于类和对象的相关知识就讲到这里希望同学们多多支持如果有不对的地方欢迎大佬指正谢谢