自学内容网 自学内容网

C++类和对象(下) 初始化列表 、static成员、友元、内部类等等

1.再探构造函数

  1. 之前使用构造函数时都是在函数体内初始化成员变量,还有一种构造函数的用法,叫做初始化列表;那么怎么使用呢?
    1. 使用方法用冒号开始(" : ")要写多个就用逗号(" , ")隔开数据成队列
    2. 每个成员变量后面跟着一个括号,括号内就是初始化的内容;括号内可以是初始值或者一个表达式
  2. 每个成员变量只要初始化一次就行了,不能重复初始化;这里也是成员变量定义的地方
  3. 引用成员变量、const成员变量、没有默认构造的类 类型变量,这三类必须在初始化列表位置进行初始化,否则会报错
  4. C++11支持在成员变量声明的位置给上缺省值,这里的缺省值是给初始化列表使用的,当没有显示写初始化列表就会用到缺省值
  5. 下列是思维动图形式的小总结
  6. 初始化列表中是按照成员变量声明处的顺序来进行初始化的,与初始化列表中写的先后顺序无关,所以建议声明的顺序和初始化列表的保持一致;当然也是和内存存储顺序有关(地址处低到高)
  7. 每个构造函数都有初始化列表,没有显示写会自动生成
  8. 补充:
    1. 函数的缺省值是提供给实参使用的,成员变量处的缺省值是提供给初始化列表使用的
    2. 调用函数建立栈帧时对象就会申请空间,构造函数是用来初始化的;两者之间要区分

#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.类型转换

  1. C++支持内置类型隐式类型转换为类类型对象需要对应的内置类型为参数(实参)的构造函数
  2. 如果不需要隐式类型转换了,构造函数前面加explicit就不再支持隐式类型转换
  3. 类类型的对象之间也可以隐式转换,需要对应构造函数支持
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成员

  1. 用static修饰的成员变量, 叫做静态成员变量,静态成员变量一定要在类外面初始化
  2. 静态成员变量为 所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区;当前同类型的对象也可以访问
  3. 被static修饰的成员函数,叫静态成员函数,静态成员函数是没有this指针的;如果要返回静态成员变量需要静态成员函数
  4. 静态成员函数中可以访问其他的静态成员,但是不能访问非静态成员,因为没有this指针
  5. 非静态的成员函数,可以任意访问静态成员函数和静态成员变量
  6. 突破类域的限制就可以访问静态成员,通过 fun3::Getb() 或者 fufu.Getb();就可以访问静态成员函数和静态成员变量
  7. 虽然可以任意访问静态成员,但静态成员也是类的成员,也会受到public、protected、private访问限定符的限制
  8. 静态成员变量,不走构造函数的初始化列表;因为不属于某一个类,自然给初始化列表的缺省值也不能用
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;
}

补充:

  1. 静态的变量第一次走到那里才会初始化;全局的静态会在,main函数之前初始化。
  2. 编译阶段会有语法检查;变量初始化时,如果不使用会被优化;

4.友元

  1. 友元提供一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明前面加上friend,把友元声明放到类里
  2. 外部的友元函数可以访问类的私有和保护成员,就是一个声明,不是类的成员,也不会占用额外空间
  3. 友元函数可以在类的任何地方定义,不受类访问限定符限制;一般都放到类的最开头
  4. 一个函数可以是多个类的友元函数
  5. 友元类中的成员函数都可以是另一个类的友元函数,可以访问另一个类的私有和保护
  6. 不过友元类是单向的,A类是B类的友元,但是B类不可以是A类的友元
  7. 友元类没有传递性,A类是B类的友元,B类可以是C类的友元,但是A类不可以是C类的友元;如果希望是那么在C类写上A类的友元声明
  8. 友元也有弊端,友元会增加耦合度,破坏了封装,尽量不用
//前置声明
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.内部类

  1. 如果一个类定义在另一个类的内部,那么这个类就叫做内部类;内部类是一个独立的类(计算外部类的空间时,不会开辟内部类的),只受到访问限定符的限制和外部类的类域限制,所以外部类定义的对象中不包含内部类
  2. 内部类本质也是一种封装,但A类和B类达成合作关系(B类频繁使用A类),此时可以考虑写一个专属内部类,就是把A类放到private/protected位置;这样其他地方想创建A类,就创建不了
  3. 可以看看这题,更好的理解

#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.匿名对象

  1. 用类型(实参),这种对象叫做匿名对象;之前的那种是有名对象 类型 对象名(实参),是有名对象
  2. 匿名对象的生命周期只有一行,和编译器生成的临时对象一样;一般临时用一下,可以用匿名对象
#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.对象拷贝时的编译器优化

  1. 现在的编译器为了提高程序的效率,不影响正确性的情况下都会进行优化;尽可能减少传值和传返回值的过程可省略的部分
  2. 有像 构造临时对象 + 拷贝构造 优化成直接构造
  3. 至于怎么优化看各自的编译器,C++并没有严格的规定;当前主流的比较新的编译器会优化连续拷贝并进行合并优化,有些编译器会更加激进的合并优化
  4. 初始化对象时,强制类型转换也是根据构造函数来看的,是需要看构造函数的参数的
#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;
}

做好自己,减少竞争性的努力,走好自己的路,超越昨天的自己


原文地址:https://blog.csdn.net/weixin_70873145/article/details/142644579

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!