您现在的位置是:首页 >技术杂谈 >【C++技能树】类和对象的使用 --初始化列表,static,友元,内部类,匿名对象的理解与使用网站首页技术杂谈
【C++技能树】类和对象的使用 --初始化列表,static,友元,内部类,匿名对象的理解与使用
Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法…感兴趣就关注我bua!
类和对象的使用
0. 初始化列表
这是一个C++的默认构造函数
class Date{
public:
Date(int year,int month,int day)
{
_year=year;
_month=month;
_day=day;
}
private:
int _year;
int _month;
int _day;
};
虽然我们大多时候混淆初始化与赋值的概念,但在这里,构造函数体中只能成为赋值,因为初始化只能初始化一次,而赋值可以赋值多次。那么在哪里进行初始化呢?可能会说在定义时直接初始化,这在日期类中是可以的,但在这种情况当中,显然是不可以的了
class A{
private:
int __a;
};
class B{
private:
int &_ref;
const int _ci;
};
根据前面所学,引用&与限制const必须在定义时就完成初始化。那么我这里该如何初始化呢?我并没有确定我的要使用的对象是什么。
这就是初始化列表存在的意义了:
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或者表达式
class B{
public:
B(int& ref,int ci):
_ref(ref),
_ci(ci)
{
}
private:
int &_ref;
const int _ci;
};
所以,这样解决了引用&与限定const的初始化问题:可以根据具体场景进行初始化了。
注意:
-
每个成员变量仅能在初始化列表中出现一次!因为初始化只能初始化一次
-
类中包含 引用、const成员变量及自定义类型成员(且该类没有默认构造函数时),必须将其放在初始化列表中进行初始化!
class A{ public: A(int a): __a(a) { } private: int __a; }; class B{ public: B(int a,int& ref,int ci): _a(a), _ref(ref), _ci(ci) { } private: A _a; const int _ci; int &_ref; };
例如在这个例子中,B类的属性有三个,分别对应了自定义类型成员,引用,const的三种情况。所以需要将其放在初始化列表中进行初始化。
其初始化顺序为,从成员属性去找对应的初始化列表。
这是一个初始化列表的特性,当遇到以下这种情况的时候就需要充分考虑到这种特性。
class bug
{
public:
bug(int a):
_a1(a),
_a2(_a1)
{
}
private:
int _a2;
int _a1;
}
当传入的a=1时_a2与_a1的结果是什么呢?答案是a2为乱码,a1为1,这就是因为初始化列表的初始化顺序是根据声明顺序来进行的,所以导致了这个问题。
在日常中建议多用初始化列表,因为即使在声明时给上初始值,之后也会调用初始化列表进行初始化。
那初始化列表这么有用,是不是在函数体内进行声明就没有使用场景了?
class test2
{
public:
test2():
a((int *)malloc(sizeof(int)*10))
{
if(a==NULL)
{
perror("malloc,failed");
}
}
private:
int *a;
};
int main()
{
test2 a;
return 0;
}
在这段代码中,虽然初始化列表可以完成对a的空间分配,但malloc可能存在分配失败的问题。所以需要对其分配完的空间进行一个检查,此时初始化列表就不能做这件事了。
所以,初始化列表只适用于初始化变量,这一件事,其他事仍然需要放在函数体内执行
explicit关键字
class trans{
public:
trans(int a=0)
{
_a=a;
cout<<"trans()";
}
trans(const trans& T)
:_a(T._a)
{
cout<<"trans(const trans&T)";
}
void print()
{
cout<<_a<<endl;
}
private:
int _a;
};
int main()
{
trans T1(1);
trans T2=2;
const trans& T3=2;
return 0;
}
T1与T2的初始化非常的常见,前面也介绍了很多类似的用法,但需注意,T2此时有一个隐式类型转换:
会先将内置类型调用一次构造函数,将其变为trans对象,之后再调用一次初始化构造,完成对T2的初始化。(但经过编译器优化后这种行为只会出现一次)
所以T3为什么需要加上const?因为在完成转换后,临时构造出的trans会被销毁,而&又不能引用一个销毁的对象,所以需要const延长其生命周期。
当然,我们如果不想发生这种隐式类型转换的行为,可以在构造函数前加入 explicit的关键字。
class trans{
public:
explicit trans(int a=0)
{
_a=a;
cout<<"trans()";
}
trans(const trans& T)
:_a(T._a)
{
cout<<"trans(const trans&T)";
}
void print()
{
cout<<_a<<endl;
}
private:
int _a;
};
int main()
{
trans T1(1);
trans T2=2;
return 0;
}
1.Static静态成员变量
看看以下这个程序,创建了多少类对象。
class sta{
public:
sta(){
_count++;
}
sta(const sta&st)
{
_count++;
}
~sta()
{
--_count;
}
static int _count;
};
int sta::_count=0;
sta aa0;
void Func()
{
static sta aa2;
cout << __LINE__ << ":" << sta::_count << endl;
sta::_count++;
}
int main()
{
cout <<__LINE__<<":"<< sta::_count << endl; // 1
sta aa1;
Func(); // 3
Func(); // 3
return 0;
}
从中可以看出static对象的作用:对于全局变量进行了进一步的封装:
- 静态成员为所有类对象所共享,不属于某个具体的对象
- 静态成员变量在类内声明,在类外定义,定义时不加static关键字
- 可以通过类名::变量名 或者 对象.静态成员来访问
- 静态成员函数没有this指针,所以不能访问任何非静态成员
- 静态成员也是类的成员,受访问限定符限制
一道用到了static变量特性的oj题
class sum
{
public:
sum()
{
n++;
s+=n;
}
static int s;
static int n;
};
int sum:: s=0;
int sum:: n=0;
class Solution {
public:
int Sum_Solution(int n) {
sum s1[n];
return sum::s;
}
};
2.友元
2.1.友元函数
友元函数可以直接访问类的私有成员,使用时在类中的任意位置声明即可,
class test0
{
public:
private:
int score=100;
};
void print(const test0& t0)
{
cout<<t0.score<<endl;
}
在这里,是无法访问score这个变量的,因为score为私有成员属性,所以无法在类外进行访问.但加入友元函数的声明后可以突破这个限制
class test0
{
friend void print(const test0&t0);
public:
private:
int score=100;
};
void print(const test0& t0)
{
cout<<t0.score<<endl;
}
- 友元函数可以访问类的私有成员和保护成员,但其不是类的成员函数
- 友元函数因为不是类的成员函数,没有this指针,所以不可以且没必要用后置const进行修饰
- 友元函数可以在类定义的任何地方进行声明
- 一个函数可以与多个类达成友元关系
- 调用原理与普通函数相同
2.2.友元类
在一个类中声明加入另一个类的友元声明,在另一个类中即可访问这个类受保护的成员变量.
class test0
{
friend void print(const test0&t0);
friend class test3;
public:
private:
int score=100;
};
class test3
{
public:
void print()
{
cout<<t0.score;
}
private:
test0 t0;
};
- 友元关系是单向的,test3可以访问test0的成员变量,反之却不可以
- 友元关系不能传递
3.内部类
一个类定义定义在一个类的里面.
class A
{
public:
class B{
private:
int _b;
};
private:
static int _a1;
int _a2;
};
int A:: _a1=0;
int main()
{
return 0;
}
计算A的大小时,仅计算A中的成员,例如这里sizeof(A)=4.
内部类是外部类的友元,访问静态变量的时候不需要加类作用域修饰符
这里B是A的友元,但A不是B的友元
class A
{
public:
class B{
public:
void print(const A&a)
{
cout<<a._a2<<_a1;
}
private:
int _b;
};
private:
static int _a1;
int _a2;
};
4.匿名对象
当想使用一个类中的某一个函数时可以直接使用匿名对象.
class C{
public:
void print()
{
cout<<6;
}
};
int main()
{
C a;
a.print();
C().print();
}
正常的调用需要像main中前两行写一样,但加入了匿名函数的规则之后,直接按照第三个规则来写即可.匿名函数的生命周期仅在当前行.
void print(const A&a)
{
cout<<a._a2<<_a1;
}
private:
int _b;
};
private:
static int _a1;
int _a2;
};
4.匿名对象
当想使用一个类中的某一个函数时可以直接使用匿名对象.
class C{
public:
void print()
{
cout<<6;
}
};
int main()
{
C a;
a.print();
C().print();
}
正常的调用需要像main中前两行写一样,但加入了匿名函数的规则之后,直接按照第三个规则来写即可.匿名函数的生命周期仅在当前行.