1.再探构造函数
- 之前使用构造函数时都是在函数体内初始化成员变量,还有一种构造函数的用法,叫做初始化列表;那么怎么使用呢?
- 使用方法用冒号开始(" : ")要写多个就用逗号(" , ")隔开数据成队列
- 每个成员变量后面跟着一个括号,括号内就是初始化的内容;括号内可以是初始值或者一个表达式
- 每个成员变量只要初始化一次就行了,不能重复初始化;这里也是成员变量定义的地方
- 引用成员变量、const成员变量、没有默认构造的类 类型变量,这三类必须在初始化列表位置进行初始化,否则会报错
- C++11支持在成员变量声明的位置给上缺省值,这里的缺省值是给初始化列表使用的,当没有显示写初始化列表就会用到缺省值
- 下列是思维动图形式的小总结
- 初始化列表中是按照成员变量声明处的顺序来进行初始化的,与初始化列表中写的先后顺序无关,所以建议声明的顺序和初始化列表的保持一致;当然也是和内存存储顺序有关(地址处低到高)
- 每个构造函数都有初始化列表,没有显示写会自动生成
- 补充:
- 函数的缺省值是提供给实参使用的,成员变量处的缺省值是提供给初始化列表使用的
- 调用函数建立栈帧时对象就会申请空间,构造函数是用来初始化的;两者之间要区分
#include<iostream>
using namespace std;
class Date
{
public:
Date(int day = 100)
{
_day = day;
}
private:
int _day;
};
class A
{
public:
A(int a = 1, int b = 1, int c = 1)
: _a(a)
, _b(b)
,_ca(c)
,_quote(a)
, _ptr((int*)malloc(12))//还可以是表达式
{
if (_ptr == nullptr)
{
perror("_prt fail");
}
else
{
memset(_ptr, 1, 12);//给内存初始化为 全1
}
}
void Print()
{
cout << _a << '/' << _b << '/' << _c << endl;
}
private:
//声明
int _a = 10; // 这里的是给初始化列表使用的缺省值
int _b = 20;
int _c = 30; //没有显示写时,缺省值就会用上
int* _ptr;
//下列的都必须使用初始化列表
const int _ca;//必须要有初始化
int& _quote;
Date _ddy;// 没有默认构造会报错
};
int main()
{
A aa(2024,8,23);
aa.Print();
return 0;
}
2.类型转换
- C++支持内置类型隐式类型转换为类类型对象,需要对应的内置类型为参数(实参)的构造函数
- 如果不需要隐式类型转换了,构造函数前面加explicit就不再支持隐式类型转换
- 类类型的对象之间也可以隐式转换,需要对应构造函数支持
class AB
{
public:
//explicit AB(char a = 0, char b = 0)
AB(char a = 0,char b = 0)
{
_a = a;
//_b = b;
}
void Print()
{
cout << _a << _b << endl;
}
private:
char _a;
char _b = 'b';
};
class Stack
{
public:
Stack()
{
}
void Push(const AB& abp)
{
//...
}
private:
AB _arr[10];
int _size;
};
int main()
{
//隐式类型转换
//65构造AB类的临时对象,然后用这个临时对象拷贝给au
//编译器将 连续构造 + 拷贝构造 ,优化成直接构造
AB au = 65;
//AB au = "abcd";字符串不可以隐式类型转换
au.Print();
AB& aup = au;
const AB& p = 2; // 2 会产生临时对象,具有常性,需要const
Stack st;
AB aa1(60, 70);
st.Push(aa1);
AB aa2 = { 88,99 };//支持多参数
// C++11 才支持的
const AB& aa3 = { 77,78 };
//上面的方法麻烦,还需要创建类再插入;这里可以直接插入
//因为AB是多参数的
st.Push({ 77,78 });
return 0;
}
3.static成员
- 用static修饰的成员变量, 叫做静态成员变量,静态成员变量一定要在类外面初始化
- 静态成员变量为 所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区;当前同类型的对象也可以访问
- 被static修饰的成员函数,叫静态成员函数,静态成员函数是没有this指针的;如果要返回静态成员变量需要静态成员函数
- 静态成员函数中可以访问其他的静态成员,但是不能访问非静态成员,因为没有this指针
- 非静态的成员函数,可以任意访问静态成员函数和静态成员变量
- 突破类域的限制就可以访问静态成员,通过 fun3::Getb() 或者 fufu.Getb();就可以访问静态成员函数和静态成员变量
- 虽然可以任意访问静态成员,但静态成员也是类的成员,也会受到public、protected、private访问限定符的限制
- 静态成员变量,不走构造函数的初始化列表;因为不属于某一个类,自然给初始化列表的缺省值也不能用
class fun2
{
public:
static int _aa;
fun2()
{
}
void sum()
{
_a--;
}
static int Geta()//不可以访问非静态的,非静态的可以访问静态的
{
_a += 2;
return _a;//虽然都可以访问静态成员变量,但是正常情况下是需要this指针才能当作返回值
//得出如果要返回静态成员变量需要静态成员函数
}
private:
static int _a;
};
class fun3
{
public:
static int Getb()
{
//调用函数 返回结果
return fun2::Geta();//因为是静态函数所以都可访问,但也受访问限定符限制
}
private:
static int _b;
};
//在类外初始化
int fun3::_b = 30;
int fun2::_aa = 22;
int fun2::_a = 10;
int main()
{
int at = 1;
fun2 f1;
f1.sum();
cout << fun2::Geta() << endl;
//大小是1,标识这个对象,静态成员变量在静态区
cout << sizeof(f1) << endl;
//都可以访问静态函数
cout << fun3::Getb() << endl;
//所有类都可以访问,但是会受到类域限制
cout << fun2::_aa << endl;
//访问静态成员函数两种方式,常用 :: 符号访问
fun3 fufu;
cout << fun3::Getb() << endl;
cout << fufu.Getb() << endl;
return 0;
}
补充:
- 静态的变量第一次走到那里才会初始化;全局的静态会在,main函数之前初始化。
- 编译阶段会有语法检查;变量初始化时,如果不使用会被优化;
4.友元
- 友元提供一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明前面加上friend,把友元声明放到类里
- 外部的友元函数可以访问类的私有和保护成员,就是一个声明,不是类的成员,也不会占用额外空间
- 友元函数可以在类的任何地方定义,不受类访问限定符限制;一般都放到类的最开头
- 一个函数可以是多个类的友元函数
- 友元类中的成员函数都可以是另一个类的友元函数,可以访问另一个类的私有和保护
- 不过友元类是单向的,A类是B类的友元,但是B类不可以是A类的友元
- 友元类没有传递性,A类是B类的友元,B类可以是C类的友元,但是A类不可以是C类的友元;如果希望是那么在C类写上A类的友元声明
- 友元也有弊端,友元会增加耦合度,破坏了封装,尽量不用
//前置声明
void Print();
class yy
{
//友元函数声明
friend int sum(const yy& x1, const yy& x2);
friend void Print();
//友元类声明,单向的;yy类不可访问cl类
friend class cl;
public:
void print2()
{
Print();
}
private:
int _a = 10;
int _b = 20;
};
int sum(const yy& x1,const yy& x2)
{
return x1._a + x2._b;
}
void Print()
{
cout << 11 << endl;
}
class cl
{
int sum1(const yy& x1, const yy& x2)
{
return x1._a + x2._b;
}
int sum3(const yy& x1, const yy& x2)
{
return x1._a + x2._b;
}
};
int main()
{
yy x1, x2;
int ret = sum(x1, x2);
cout << ret << endl;
//此时证明写了友元函数声明,可以双方互相访问
x1.print2();
return 0;
}
5.内部类
- 如果一个类定义在另一个类的内部,那么这个类就叫做内部类;内部类是一个独立的类(计算外部类的空间时,不会开辟内部类的),只受到访问限定符的限制和外部类的类域限制,所以外部类定义的对象中不包含内部类
- 内部类本质也是一种封装,但A类和B类达成合作关系(B类频繁使用A类),此时可以考虑写一个专属内部类,就是把A类放到private/protected位置;这样其他地方想创建A类,就创建不了
- 可以看看这题,更好的理解
#include <iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << _a << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a = 10;
class B
{
private:
int _x;
double _y;
};
};
//匿名对象
int main()
{
A aa;
aa.Print();
//A aa(); //此时不确定是函数还是类
cout << sizeof(aa) << endl;//计算的大小是4,并不包含B类
return 0;
}
6.匿名对象
- 用类型(实参),这种对象叫做匿名对象;之前的那种是有名对象 类型 对象名(实参),是有名对象
- 匿名对象的生命周期只有一行,和编译器生成的临时对象一样;一般临时用一下,可以用匿名对象
#include <iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << _a << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a = 10;
};
int main()
{
A aa;
aa.Print();
//A aa(); //此时不确定是函数还是类
//匿名对象,和临时对象一样声明周期只有一行,
A();
//匿名对象,可以直接调用函数;对比上面要少写一行
A().Print();
return 0;
}
7.对象拷贝时的编译器优化
- 现在的编译器为了提高程序的效率,不影响正确性的情况下都会进行优化;尽可能减少传值和传返回值的过程可省略的部分
- 有像 构造临时对象 + 拷贝构造 优化成直接构造
- 至于怎么优化看各自的编译器,C++并没有严格的规定;当前主流的比较新的编译器会优化连续拷贝并进行合并优化,有些编译器会更加激进的合并优化
- 初始化对象时,强制类型转换也是根据构造函数来看的,是需要看构造函数的参数的
#include <iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a = 1)" << endl;
}
//拷贝构造
A(const A& xx)
:_a(xx._a)
{
cout << "A(const A & xx)" << endl;
}
~A()
{
cout << _a << "~A()" << endl;
}
void Print()
{
cout << _a << endl;
}
A& operator++()
{
_a+= 100;
return *this;
}
A& operator=(const A& xx)
{
cout << "A& operator=(const A& xx)" << endl;
if (this != &xx)//不能和自己相同,否则就不对了
{
_a = xx._a;
}
return *this;
}
private:
int _a;
};
void fun1(A aa)
{
}
//初始化对象时
//int main()
//{
// A tmp = 1;// 构造一个临时对象 + 拷贝构造 都会转化成直接构造
//
// const A& at = 10;//直接会强制类型转换,这个强制类型转换也是根据构造函数来看的
// return 0;
//}
//传参时的优化
//int main()
//{
// A aa(1);
// fun1(aa);//没有使用引用,会产生拷贝构造,出函数临时对象销毁
//
// cout << endl;
//
// //有优化
// fun1(A(20));
//
// cout << endl;
//
// //有优化
// fun1(30);//构造临时对象 + 拷贝构造 优化成直接构造
// return 0;
//}
//返回值
A fun2()
{
A aa(1);
++aa;
cout << "------" << endl;
return aa;//vs2022 优化比19版 更加激进;把aa的构造,拿101直接构造临时对象,并作为返回值
}
//int main()
//{
// fun2().Print();//使用函数的临时对象调用函数,并且临时对象的生命周期只在这一行
// cout << "********" << endl;
// return 0;
//}
int main()
{
A ret;
ret = fun2();// 拿临时对象去赋值拷贝构造; 只优化了fun2()的拷贝构造
ret.Print();
cout << "**********" << endl << endl;
return 0;
}
做好自己,减少竞争性的努力,走好自己的路,超越昨天的自己