什么是类
- 类是一种用户定义(数据)类型。,类似与C语言的结构体
- 无论是什么编程语言,进行面向对象程序设计都是从类的设计开始的。类则是C++面向对象编程的实现方式
类的声明与定义
语法:
类关键词:class、struct、union 之一
类可拥有下列种类的成员:
- 数据成员
a) 非静态数据成员,包括位域。
b) 静态数据成员- 成员函数
a) 非静态成员函数
b) 静态成员函数- 嵌套类型
一些成员函数是特殊的:某些条件下,即使用户不定义,编译器也会定义它们。:
类的声明和定义
我们来看个例子,首先声明一个student类
class student
{
//可以在此处声明类成员变量和成员函数: 用于描述student的各方面特性,例如姓名、学号、年龄等信息。
};
声明了student数据类型之后,我们就可以用其定义变量了(在定义类的对象时,class关键字可要可不要,如下,但通常出于习惯我们通常都会省略掉class关键字),如:
// 定义单个变量
student LiLei; //创建student类的对象。
class student LiLei; //同样正确
// 定义一个数组或者指针
student all_student[1000];
student * pointer;
在用类定义对象的时候,一定要先给出类声明,这就好比用某种自定义数据类型来定义变量的时候,我们必须要先给出该数据类型的声明一样。由于C++里面本身集成一些常用数据类型,如int、bool、double等,所以在用这些数据类型声明变量时不需要再由我们自己给出类型声明了。
类中的成员变量与成员函数
类是一种数据类型,该类型类似于普通的数据类型,但是又有别于普通的数据类型。类这种数据类型是一个包含成员变量和成员函数的一个集合。类中成分可以分为四种
静态数据成员
语法:
static 数据成员
说明:
- 是类的一部分但不是某个类对象的一部分的变量称为static成员。static成员只有唯一副本,而不像非static成员那样每个对象都有其副本
- 静态数据成员不关联到任何对象。即使不定义类的任何对象它们也存在。也就是说,类的所有对象都有权访问它的同一副本
- 整个程序只有一个拥有静态存储期(
static
)的静态数据成员实例,除非使用关键词 thread_local,该情况下每个线程都有一个具有线程存储期的该对象。 - 静态数据成员不能为 mutable。
- (C++17 起),静态数据成员可以声明为 inline
struct X
{
inline static int n = 1;
};
非静态数据成员
- 不允许使用 extern 和
register(不建议使用,C++17起已经被弃用)
存储类说明符; - 不允许使用 thread_local 存储类说明符(但允许 static 数据成员);
#include <string>
class S
{
int n; // 非静态数据成员
int& r; // 引用类型的非静态数据成员
int a[2] = {1, 2}; // 带默认成员初始化器的非静态数据成员 (C++11)
std::string s, *ps; // 两个非静态数据成员
struct NestedS {
std::string s;
} d5; // 具有嵌套类型的非静态数据成员
char bit : 2; // 2 位的位域
};
静态成员函数
语法:
说明:
- 静态成员函数不关联到任何对象。调用时,它们无 this 指针。
- 它只能访问类的静态数据成员
- 静态成员函数不能为 virtual、const 或 volatile。
- 静态成员函数的地址可以存储在常规的函数指针中,但不能存储于成员函数指针中。
- 需要访问类成员而不需要通过特定对象调用的函数叫做static成员函数
class Date{
int d, m, y;
static Date default_date;
public:
Date(int dd = 0, int mm = 0, int yy = 0);
static void set_default(int dd, int mm, int yy);
};
Date::Date(int dd, int mm, int yy){
d = dd ? dd : default_date.d;
m = mm ? mm : default_date.m;
y = yy ? yy : default_date.y;
}
使用set_default(),可以在恰当的时候改变默认值。
可以像引用任何其他成员一样引用static成员。此外,不必提及任何对象即可引用static成员,方法是使用其类的名字作为限定,比如:
void f(){
Date::set_default(4, 5, 1995);
}
如果使用了 static函数或者静态成员(没有使用就不必定义),我们就必须在某处定义它们。在static成员的定义中不必重复关键字static
Date Date::default_date {16, 1, 1995};
void Date::set_default(int dd, int mm, int yy){
default_date = {dd, mm, yy};
}
注意,Date{}表示Date::default_date 的值:
Date copy_of_default_copy = Date{};
void f(Date);
void g(){
f(Date{});
}
因此,我们不需要一个独立的函数来读取默认值。而且,当模板类型为Date无疑时,更简单的{}就足够了:
void f1(Date);
void f2(Date);
void f2(int);
void g(){
f1({}); //ok:等价于f1(Date{});
f2({}) ; //error: 二义性,f2(int)还是f2(Date)
f2(Date{});
}
在多线程代码中,static数据成员需要某种锁机制或访问规则来避免竞争条件。
非静态成员函数
- 非静态成员函数是声明于类的成员说明中,不带 static 或 friend 说明符的函数。
class S {
int mf1(); // 非静态成员函数声明
void mf2() volatile, mf3() &&; // 可为 cv 限定或引用限定
int mf4() const { return data; } // 可内联定义
virtual void mf5() final; // 可为虚函数,可使用 final/override
S() : data(12) {} // 构造函数亦是成员函数
int data;
};
int S::mf1() { return 7; } // 若不内联定义,则必须定义于命名空间
成员初始化
从C++11起,非静态成员声明符可以包含初始值设定项:
class CanInit
{
public:
long num {7}; // OK in C++11
int k = 9; // OK in C++11
static int i = 9; // Error: must be defined and initialized
// outside of class declaration.
// initializes num to 7 and k to 9
CanInit(){}
// overwrites original initialized value of num:
CanInit(int val) : num(val) {}
};
int main()
{
}
如果该构造函数对一个成员分配了一个值,则该值将覆盖声明时用于初始化该成员的值。
对于给定类类型的所有对象,只有一个静态数据成员的共享副本。必须在文件范围内定义静态数据成员并可在此范围内将其初始化
// class_members2.cpp
class CanInit2
{
public:
CanInit2() {} // Initializes num to 7 when new objects of type
// CanInit are created.
long num {7};
static int i;
static int j;
};
// At file scope:
// i is defined at file scope and initialized to 15.
// The initializer is evaluated in the scope of CanInit.
int CanInit2::i = 15;
// The right side of the initializer is in the scope
// of the object being initialized
int CanInit2::j = i;
类内初始化和成员初始化不是一回事
局部类
类声明可以出现在函数体内,该情况下它定义局部类。这种类的名字只存在于函数作用域中,且无法在函数外访问。
- 局部类不能拥有静态数据成员
- 局部类的成员函数无连接
- 局部类的成员函数必须完全在类体内定义
- 除闭包类型以外的 (C++14 起)局部类不能拥有成员模板
- 局部类不能拥有友元模板
- 局部类不能在类定义内定义友元函数
- 函数(包括成员函数)内的局部类可以访问其外围函数能访问的相同名字。
- 局部类不能用作模板实参
(C++11 前)
#include <vector>
#include <algorithm>
#include <iostream>
int main()
{
std::vector<int> v{1,2,3};
struct Local {
bool operator()(int n, int m) {
return n > m;
}
};
std::sort(v.begin(), v.end(), Local()); // C++11 起
for(int n: v) std::cout << n << ' ';
}
成员访问
- 可以通过对类X的对象使用
.
访问X的成员 - 可以通过对类X的对象的指针使用
->
访问X的成员
struct X{
int m;
void f();
};
void user(X x, X *px){
x.m = 11;
px->m = 1;
}
- 在类内部访问成员不需要任何运算符:
void X::f(){
m = 1; //相当于this->m = 1;
}
- 成员函数可以在一个成员声明前就引用他:
struct X{
void f() {return x};
int m;
};
- 如果我们希望引用一个类的公共成员,而不是某个特定对象的成员,应该使用
类名::xxx
限定方式:
struct X{
int m;
int f();
static int sm;
};
int X::f() {return m};
X::sm{7};
int (X::)pmf() {&S::f};
建议
- 将概念表示为类
- 将类的实现与接口分类
- 定义构造函数来处理对象初始化
- 默认将单参数构造函数声明为explicit
- 将不修改其对象状态(值)的成员函数声明为const成员函数
- 具体类型是最简单的类。只要适用,就应该优先选择具体类型而不是更复杂的类或者普通数据结构
- 仅当函数需要直接访问类的表示时才将其声明为成员函数
- 使用名字空间建立类与其辅助函数间的显式关联
- 如果一个函数需要访问类的表示,但是不需要使用某个具体对象来调用,建议将其实现为static成员函数*
http://www.weixueyuan.net/view/6333.html