背景
C++中最重要的一个概念大概就是类和对象了,最近看了一些Skia的源码,就发现里面类的声明有很多中,自己在动手写的时候多少还是有些不知所措。所以就参考这个tutorial再学习一下C++里面类的语法,和每种写法的场景。
类的定义
首先,类是structure的一个延伸,struct主要是包含成员变量,class的话还可以包含成员函数。
另外,object
和class
的区别在于,object
是class
的一个实例,class
是一个type,而object
是一个变量。
类的使用语法就是使用class
或者struct
关键字:
class 类名 {
public:
// 公共的行为或属性
protected:
// 受保护的行为或属性
private:
// 私有的行为或属性
} 实例变量名;
其中实例变量名是一个可选的变量名列表。access specifiers
是指里面的访问控制修饰符,是来控制成员访问的权限的:
-
private
修饰的成员只能从相同类(或者friend)里的其他成员进行访问 -
protected
修饰的成员可以从相同类(或者friend)里的其他成员之外,还可以从派生类(子类)中进行访问,也就是private访问权限之外加一个子类 -
public
修饰的成员就可以从任何地方进行访问
在class关键字里面声明的所有成员默认都是private的。所以下面的写法相当于写width和height都是私有成员:
class Rectangle {
int width, height;
public:
void set_values (int,int);
int area (void);
} rect;
上面还有一个注意的是,Reactangle是类,rect是实例变量,这里只有声明,还没有到定义。这个关系就像下面的int
和a
的关系,int
是类名,a
是变量名。
int a;
这里面私有和公有成员的访问权限就不细说了,应该大家都知道。成员函数的实现也可以通过::
两个冒号在class之外进行定义,比如下面这样:
// classes example
#include
using namespace std;
class Rectangle {
int width, height;
public:
void set_values (int,int);
int area() {return width*height;}
};
void Rectangle::set_values (int x, int y) {
width = x;
height = y;
}
int main () {
Rectangle rect;
rect.set_values (3,4);
cout << "area: " << rect.area();
return 0;
}
上面这个例子里面,area
方法是直接在class里面定义的,而set_values
方法是在class之外定义的。通过双冒号来区别于其他普通的非成员函数。在class内定义和class外定义成员方法的区别在于,在class类内定义的成员函数被编译器当作是inline成员函数
,而外面的话只是一个普通的类成员函数,但这只是对编译器优化来说有区别,对使用没有区别。
构造函数
这个时候我们可以看到,如果成员函数area
在set_values
之前就被调用怎么办?就会返回一个未定义的成员变量width
和height
。为了避免这种情况,类可以包含一个特殊的构造函数,当每次初始化时可以被自动的调用,初始化成员变量和内存的分配。
构造函数被声明就像一个普通的成员函数,但是名字和类名一样,并且没有返回类型,void
都没有。它只会在生成实例的时候被执行一次,且没有返回值!!只是一个简单的对象初始化。
构造函数重载
和其他的函数一样,构造函数也可以通过不同的参数(不同的参数个数或者参数类型)来进行重载,编译器会自动的调用匹配参数的函数。比如下面这个例子中:
// overloading class constructors
#include
using namespace std;
class Rectangle {
int width, height;
public:
Rectangle ();
Rectangle (int,int);
int area (void) {return (width*height);}
};
Rectangle::Rectangle () {
width = 5;
height = 5;
}
Rectangle::Rectangle (int a, int b) {
width = a;
height = b;
}
int main () {
Rectangle rect (3,4); // 12
Rectangle rectb; // 25
cout << "rect area: " << rect.area() << endl;
cout << "rectb area: " << rectb.area() << endl;
return 0;
}
这个里面下面2个都是调用构造函数的:
Rectangle rectc(3,4);
Rectangle rectb;
但是也有差别,rectb的构造是调用了默认的构造函数,而rect调用的是接受a,b入参的重载的构造函数。这里面还要注意的是默认构造函数不能用下面的方式来调用:
Rectangle rectc();
加上括号之后就不能调用默认构造函数,而是要调用一个不接受入参的重载构造函数了。
统一的初始化
像上面那样调用构造函数的方法是把入参传入括号中的初始化方式,是函数式的形式(functional form
)。但是构造函数也可以用其他的语法来调用。只有一个入参的构造函数可以用下面的方式:
class_name object_name = initialization_value;
最近的话C++又有了uniform initialization
的初始化语法,和上面函数式的形式一样,可以用大括号{}替代中括号()来包裹参数。
// classes and uniform initialization
#include
using namespace std;
class Circle {
double radius;
public:
Circle(double r) { radius = r; }
double circum() {return 2*radius*3.14159265;}
};
int main () {
Circle foo (10.0); // functional form
Circle bar = 20.0; // assignment init.
Circle baz {30.0}; // uniform init.
Circle qux = {40.0}; // POD-like
cout << "foo's circumference: " << foo.circum() << '\n';
return 0;
}
在之前的例子的话就是
Rectangle rectb; // default constructor called
Rectangle rectc(); // function declaration (default constructor NOT called)
Rectangle rectd{}; // default constructor called
这个形式的选择只是风格的问题,没有其他的含义。
构造函数中的成员初始化
成员变量的初始化可以不用函数体就直接完成,语法就是在构造函数之前用一个冒号(:
)和一个初始化的列表来声明,比如下面这个构造函数Rectangle:
class Rectangle {
int width,height;
public:
Rectangle(int,int);
int area() {return width*height;}
};
可以用下面几种方式来写:
Rectangle::Rectangle (int x, int y) { width=x; height=y; }
Rectangle::Rectangle (int x, int y) : width(x) { height=y; }
Rectangle::Rectangle (int x, int y) : width(x), height(y) { }
这上面第一种是正常的用一个函数体来写,后面2种是width(x), { height=y; }, height(y){} 。
如果是基础类型的成员变量,上面的方式就可以进行初始化,但是如果成员变量是一个对象的时候,当没有被初始化赋值时,就会调用这个对象的默认构造函数。
类的指针
对象也可以通过指针来指向,一旦被神明来,这个类就成为了一个有效的type,可以用这个类型的指针来使用,比如:
Rectangle * prect;
是一个指向类Rectangle的指针。和结构struct里面一样,对象的成员的调用是用 .
操作符,而指针是用->
操作符。
// pointer to classes example
#include
using namespace std;
class Rectangle {
int width, height;
public:
Rectangle(int x, int y) : width(x), height(y) {}
int area(void) { return width * height; }
};
int main() {
Rectangle obj (3, 4);
Rectangle * foo, * bar, * baz;
foo = &obj;
bar = new Rectangle (5, 6);
baz = new Rectangle[2] { {2,5}, {3,6} };
cout << "obj's area: " << obj.area() << '\n';
cout << "*foo's area: " << foo->area() << '\n';
cout << "*bar's area: " << bar->area() << '\n';
cout << "baz[0]'s area:" << baz[0].area() << '\n';
cout << "baz[1]'s area:" << baz[1].area() << '\n';
delete bar;
delete[] baz;
return 0;
}