1. 类
1.1 类声明与类定义
(1)类声明
class a;//声明一个类,而不是定义
在声明之后,定义之前,类a是一个不完全类型,即已知类a是一个类型,但不知道其包含了哪些成员。
不完整类型使用方式是有限的。不能定义不完整类型的对象,不完全类型只能定义指向该类型的指针或者引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数
在创建类的对象之前,必须要完整的定义该类,而不只是声明。(定义是要分配存储空间的,定义即声明,但声明不是定义)同样,在使用引用和指针访问对象成员之前,必须已经定义了类。
(2)为类的成员使用类声明
只有当类定义已经在前面出现过,数据成员才能被指定为该类类型。如果该类型是不完全类型,那么数据成员只能是指向该类类型的指针或引用。
因为只有当类定义体完成后才能定义类,因此类不能具有自身类型的数据成员,然而,只要类名一出现,就可以认为该类已经声明。因此,类的数据成员可以是指向自身类型的指针或引用:
class val{
int time;
int price;
};
class node{
val v;//因为val类前面已经定义了,所以可以用val类来定义对象
node *next;//因为在这个地方node类还没有完全定义,所以属于不完全类型,因此不能出现用node类定义对象,但只要类名出现,就可以认为声明了,所以可以用node类来定义指向该类型的指针或引用
};
1.2 对象的存储空间
- 一般类定义的时候不进行存储空间的分配(有静态数据成员的,会在定义类的时候为其分配空间),而在用类定义对象的时候,将为这个类的对象进行存储空间的分配。
- 对象的存储空间主要包含的是类的数据成员部分所占的空间
- 类的大小(对象的大小),一般指非静态数据成员的大小,要考虑到数据对齐,不包含成员方法,因为那是代码,存放在代码区,且所有对象共有,公用的成员方法通过隐含的this指针判断对象调用的,然后该方法就可以去访问那个对象的数据空间了。
- 如果类中包含虚函数,那么类对象在内存中还会被自动包含一个指向虚函数表的指针,也是占空间的, 一般这个指针在对象所在内存中起始地址处。
- 如果类是没有定义任何数据成员,类的大小或对象的大小本应该是0,但因为一个对象往往是独一无二的,体现在内存中,是不同对象的地址是不同的,所以编译器会让这个空对像占一个字节,以便不同对象的起始地址不同。
1.3 static成员
(1)static成员函数
- static成员函数没有this指针,因为static成员,包括数据成员和成员函数,都是类的组成部分,而不是任何对象的组成部分,因此static方法是没有this指针的。
- 也是因为static成员不属于任何对象,因此static成员函数不能访问任何对象的数据成员,他只能访问类的static数据成员或其他的static成员方法。
- static成员函数不能声明为虚函数。
(2)static数据成员
普通static数据成员
即非const的static数据成员,它必须在类定义体的外部定义,只定义一次。不像普通的数据成员,static数据成员不是通过构造函数进行初始化的。
class A{ private://public也行 static int i; .... }; static int A::i = 1;//error int A::i = 1;//ok int A::i;//ok,不显示初始化也正确,到时候会默认初始化为0
【注意1】:这里static关键字只能用于类定义体内部,在外面定义时不能标识为static。
【注意2】:且不管static数据成员是私有的还是共有的,都可以在类外定义初始化。
【注意3】:sizeof类时候,计算的类大小是不包含static成员的,因为静态成员不占类和对象的空间。同理可以说明:静态成员的地址并不在对象的地址范围内
【注意4!!!!!】:static变量必须在类外初始化,否则会报错。因为类的定义不占空间,类的定义只是给编译器说明这个类型有多大,好在定义对象的时候分配空间,因此不像变量的定义的那样,定义即分配空间,这里类的定义中,所有数据成员都只是声明,static类型也不例外,那为什么说static数据成员在类定义时就分配了空间了呢?因为static数据成员必须在类外定义,这个定义可以显示初始化,但一定要有这个类外的定义!!!!
特殊的const static数据成员
- 一般而言,类的static数据成员,和普通数据成员一样,不能在类的定义体中初始化。
这个规则有个例外,如果是const static数据成员,则可以在类体中定义该数据成员时初始化。
class A{ private://public也行 const static int i = 1;//ok };
static成员不是类对象的组成部分
static数据成员的类型可以是该成员所属的类类型,而非static数据成员被限定声明为指向其自身类类型的指针或引用。
class A{ private: static A mem1;//ok A mem2;//error A &mem3;//ok A *mem4;//ok }; A::mem1(..)//static 类对象的构造函数
1.4 构造函数
(1)构造函数初始化列表
构造函数的工作是保证每个对象的数据成员(除去static,因为static成员不属于对象)具有合适的初始值,一般建议使用构造函数初始化列表。
构造函数初始化列表是以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟着一个放在圆括号中的初始化式
class A{
private:
int a;
char c;
string s;
public:
A():a(1),c('a'),s("hello");//erro
A();
或
A():a(1),c('a'),s("hello")//ok
{
}
};
A::A():a(1),c('a'),s("hello");//ok
构造函数初始化列表不能在构造函数声明的时候出现,只在构造函数的定义时出现。
构造函数分两个阶段执行:
- 先执行初始化阶段(即执行初始化列表,属于显式初始化)
- 然后执行构造函数函数体阶段,该阶段不是初始化数据了,而是重新赋值。
对于在构造函数初始化列表中没有显式提及的每个成员
- 内置类型(int、char、double、枚举等)或者复合类型(数组指针等)的成员的初始化值依赖于对象的作用域:
- 对象在局部作用域中时,这些成员不被初始化
- 对象在全局作用域中它们被初始化为0
- 对于类类型数据成员,不管在哪个作用域,都运行该类型的默认构造函数来初始化,所以对于没有定义默认构造函数(无形参的构造函数)的类类型数据成员,一定要显式地在构造函数初始化列表中调用该类的带参数的构造函数。
总之:对于用类定义一个对象,会执行对象的构造函数,而执行构造函数,首先会执行构造函数初始化列表,如果有数据成员不在初始化列表中,则按上述原则进行初始化。然后再去执行构造函数函数体,但已经不是初始化了,而是重新赋值。
class A{
public:
int i;
A():i(1024){}
};
class B{
public:
int j;
A a;//因为在类的定义中,所以这是声明,不是定义对象a,定义是要分配空间的。
}
void fun(){
B b;
cout << b.j <<" "<<b.a.i<<endl;
}
B gb;
int main(){
cout << gb.j<<" "<<gb.a.i<<endl;
fun();
return 0;
}
输出的值是
0 1024
不确定值 1024
如果是以下情况:
class A