@TOC
定义类
我们暂时先用struct来定义类,到类的封装封装再来讲struct和class的区别。
下面是一个简单的类。
struct exmple{
exmple(int s):a(s){
}; //这是一个构造函数
int content() const {
return a;} //一个成员函数
int a;//一个数据成员
};
void print(exmple &temp) //一个非成员函数
{
std::cout<<temp.content();
};
成员和非成员函数
数据成员
数据成员即定义在类内的对象。可以内置类型如int,double;或是其他的类如vector等。在例子中如a.
成员函数
成员函数必须在类内声明,可在类内或内外定义。
在上面的例子中一个成员函数
int content() const {
return a;} //一个成员函数
我可以看出成员函数的定义和普通的函数定义没有什么区别。
this
奇怪的是,我们并没有传入参数,那么它是怎么得到a的值呢。我们使用了点运算符来调用它,当我们调用成员函数时,实际上是在替某个对象调用它。
我们调用a.content(),类似于content(&a)。
当我们调用成员函数时,通过一个叫this的额外隐式参数来访问对象。任何对类成员的直接访问都被看作this的隐式调用。我们访问a相当于访问this->a一样。
需要注意的是,我们不能再在函数中定义一个叫this的变量了。
const
我们可以看到例中,在形参圆括号后有一个const,实际上这个const是修饰this的,当我们不需要修改this的成员时,就可以加上const避免不必要的修改。
在外部定义成员函数
我们也可以将成员函数的声明写类的内部,在外部进行定义。
struct exmple{
exmple(int s):a(s){
};
int content();
int a;
};
int exmple::content ()
{
return a;
}
};
值得注意的,我们必须显示的说明这个函数是exmple类的成员函数,当我们使用exmple::content时,作用域就变更成类的。
非成员函数
非成员函数的声明和定义都在类的外部。非成员函数大多作为接口存在,但它们不属于类本身。
istream & read(istream &a,example &q)
{
a>>q.a;
return a;
}
这个非成员函数可以读入一个输入流的引用和一个example的引用,并读入一个数字赋值给example的a成员。
构造函数
我们知道类中可以存在很多的数据成员,我们必须知道如何初始化它们。构造函数就是用来初始化数据成员的。无论何时,只要类的对象被创建,就会执行构造函数。
exmple(int c):a(c){
};
构造函数没有返回值,它可以接受参数。:后就可以使用形参来对数据成员进行初始化。构造函数的函数体一般为空。
当然,也可以在函数体执行语句。
example(int c){
a=c};
但是没有必要
默认构造函数
例子
example():a(0){
};
当我们不提供任何参数时,就会执行默认构造函数。
如
example q;//q指向默认构造函数
合成的默认构造函数
当类内没有默认构造函数时,编译器就会自行合成一个。合成的默认构造函数会对没有类内初值的数据对象进行默认初始化;而我们知道内置类型的默认初始化后的值一般是未定义的。
在以下三种情况下不能依赖合成的默认构造函数
1.当我们类内已经存在一个构造函数时,编译器将不会再为我们合成默认的构造函数。
此时我们可以调用语句显式的要求编译来合成。
//在类内
example()=defalut;
2.当类内存在另一个没有默认构造函数的类时。此时,当前类不知道如何初始化它的类数据成员,所以无法使用合成的默认构造函数。
3.当我们使用内置类型,或指针和引用的数据成员时,默认的构造函数可能会造成错误的操作。如内置类型的默认初始化的值是未定义的。
拷贝,赋值,析构
除了定义类的对象如何初始化之外,类还有控制拷贝,赋值和销毁对象时发生的行为。当我们不主动定义这些操作时,则编译器会替我们合成。
但是某些类无法依赖合成的函数,如动态分配内存的类,值得注意的时,我们通常可以用vector或string来避免分配和释放内存带来的复杂性。
类的封装
我们可以使用访问说明符来对类进行封装,隐藏类的内部实现。
class和struct
class和struct都可以用于定义类类型;
二者的区别在于class默认所有成员为private;而struct 默认所有成员为public.
public和private
public:定义在public之后的成员可在整个程序中访问。
private:定义在private说明符之后的成员可被类的成员函数访问,但是不能被使用该类的代码访问,特别的是,被定义为友元的函数也可以访问。
class example{
public:
example(int c):a(c){
};
int content() const {
return a;};
private:
int a;
};
int main()
{
example p(3);
//不能访问一个private成员
p.a=3;
}
友元
类可以允许其他类或函数访问它的非公有函数,方法是令其他类或函数成为它的友元。
class example{
friend void change(example &a);
public:
example(int c):a(c){
};
int content() const {
return a;};
private:
int a;
};
void change(example &temp)
{
temp.a=1;
};
值得一提的是友元没有传递性,a是b的友元,b是c的友元,此时a并不是c的友元.
类的作用域
每一个类都会定义它自己的作用域,在类的作用域之外,普通的数据和函数成员只能有对象,引用或者指针使用成员访问运算符来访问。对类类型成员则使用作用域运算符来访问。
class exmaple{
public:
typdef int pos;
private:
pos a;
}
int main()
{
exmaple::pos a=1;//使用作用域描述来访问。
}
定义在类外部的成员
每一个类都会定义它自己的作用域,这样不难理解为什么在类外定义一个成员函数需要使用作用域描述符。
类的其他特性
可变成员
一个可变数据成员永远不会是const,即使它是const对象的成员
struct{
public:
void a_up() const{
a++};//即使是一个const对象,也可以修改a
private:
mutable size a=0;
}
静态成员
静态成员只存在一个且被所有拥有它的类对象共享,但一个static成员不与任何对象绑定在一起,作为结果,静态成员函数无法声明成const的且我们也不能在static函数体内使用this指针。