cpp类(一):基础知识
这些对象并不”抽象“,它们像int和float一样实际
——Doug Mcllroy
C++类概念的目标是提供一种建立新类型的工具,使得新类型的使用像内部类型一样方便。此外,派生类和模板提供了组织的方法,使得类与类之间的关联更为密切
一个类型是某概念的具体体现,例如C++内部类型float
及其四则运算是对数学中实数概念的一个具体近似,一个类就是一个用户定义类型。这一节我们学习定义类,创建类的对象,操作类的对象基本功能
定义类
类的定义十分简单,就像下面这样
class <classname>{
//...
};
{}
里包括起来的,有这个类的属性以及成员函数,我们还通过关键字private
, public
, protected
控制访问权限。
以一个具体的Date
为例,它是这样定义的
class Date
{
private:
int day, month, year; //Date对象具有day, month, year这些属性,它们是int类型的
char * description=" "; //Date对象还有description属性,是一个字符串
public:
void init(int day, int month, int year, char *description) //这是一个成员函数
{
this->day = day;
//this 是一个特殊的指针,这一点在下面说明,
//这条语句是将形参day赋值给Date对象中的day
this->month = month;
this->year = year;
this->description = description;
}
void print() //这又是一个成员函数,定义起来和普通函数无异
{
cout<<setw(4)<<"Year"<<setw(8)<<"Month"<<setw(10)<<"Day"<<endl;
cout<<setw(2)<<this->year<<setw(8)<<this->month<<setw(8)<<this->day;
if (description) cout<<"\nThat day is: "<<description;
}
};
创建对象
我们可以通过
<classname> object_name;
这样的语法实例化一个类,即是创建一个这个类的对象,一个特定类的对象意味着我们可以该对象有该类的属性以及成员函数。
通过点号(.
)标记法,我们可以访问该对象的属性值,也可以调用成员函数,过程就像下面这样
Date my_date;
my_date.init(8, 3, 2019, "Women's day");
my_date.print();
在这里,调用print()
函数会把对象的属性等显示出来,输出结果就是
Year Month Day
2019 3 8
That day is: Women's day
创建类的一些细节
以上就是关于类的非常基础的内容,下面我们关注在创建一个类的过程中一些细节。
-
this指针
对于一般的类成员函数,它们都只涉及到一个对象,即是调用它的对象,当我们对该对象的属性进行访问时,实际有一个this
指针在这里起到作用,这种作用在涉及到两个对象时尤为关键,
注意下面对Date
的一点改进,我们定义了cmp_year()
函数去比较两个Date
对象中哪一个年份更大.由于涉及到两个对象,用this
指针更加明确
...
bool cmp_year(Date & d)
{
return (this->year > d.year);
}
...
Date my_new_date = Date(2019, 3, 9);
Date yr_new_date(2018, 5, 1, "Labor's day");
if(my_new_date.cmp_year(yr_new_date)){
cout<<"My_New_Date is Bigger.";
}else{
cout<<"My_New_Date is Smaller";
}
...
-
访问控制:首先我们关注
private
,public
这两个访问标识符,private
标识下的成员无法被外界访问(除去一些将在之后说明的特殊情况),例如
void main(){
Date my_date; //这里是将Date类实例化,产生对象my_date
my_date.day = 1; //试图对my_date的day属性赋值,出现错误
...
}
这样做便是错误的,因为day
是私有的,无法被外界访问.
-
构造函数:在
Date
的创建过程中,有一个init
函数显然是为了初始化对象的,实际上使用构造函数可以更准确地做到这一点
我们对Date
类进行下面的改造,
...
public:
Date(int year, int month, int day)
{
this->year = year;
this->day = day;
this->month = month;
}
Date(int year, int month, int day, char * description)
{
this->day = day;
this->month = month;
this->year = year;
this->description = description;
}
Date()
{
this->day = 1;
this->month = 1;
this->year = 1;
this->description = "Default Setting";
}
上面的成员函数Date()
就是构造函数,我们可以发现构造函数的特点:
- 不写返回值
- 必须与该类同名
此外,示例代码还告诉我们函数重载在这里同样有效,实际上重载的规则也是一致的
当有构造函数且要初始化对象时,可以像下面这样做
Date my_new_date = Date(2019, 3, 9);
//严格形式,这里的构造函数对应Date(int year, int month, int day)
Date yr_new_date(2019, 5, 1, "Labor's day");
//简写,对应Date(int year, int month, int day, char * description)
Date default_date;
//这里没有向构造函数传值,所以调用的是没有参数的函数Date()
对三个对象均调用print()
,有如下结果:
Year Month Day
2019 3 9
That day is:
Year Month Day
2019 5 1
That day is: Labor's day
Year Month Day
1 1 1
That day is: Default Setting
还可以对构造函数进一步细分,有两类特殊的构造函数值得注意:
- 默认构造函数
它是在未提供显式初始值时用来创建对象的构造函数,像是这个语句Date my_date
,实际上C++提供了默认构造函数,是隐式的,不做任何工作,对于Date
而言,默认构造函数如同Stock::Stock() { }
当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数。
- 析构函数:构造函数在一个对象被初始化时被调用,而析构函数则在某对象被销毁时调用,逻辑都是一样的,析构函数的命名规则是:
~<classname>(){...};
这里不再具体给出示例
-
静态成员:类里面的静态成员以
static
指明,这一成员较为特殊,一个static
成员只有唯一的一份副本,而不像常规的非static
成员那样在每个在每个对象中均有一份副本,最重要的一是静态成员(可以是某属性也可以是某成员函数),不再属于某一特定的对象,而是在整个类的作用域都起作用,只要在类中声明静态成员变量,即使不定义对象,也可以为静态成员变量分配空间,进而可以使用静态成员变量。
看下面的例子
class Test
{
static int temp;
public:
static int getData()
{
return temp;
}
};
int Test::temp=3;
int main()
{
cout <<Test::getData()<<endl;
return 0;
}
运行得到的结果是3
,总结以下静态成员的特点:
- 用
static
修饰 - 静态成员必须在类外初始化,如例子中的
temp
- 静态函数可以被类调用,写成
Test::getData()
这样的形式,普通的成员函数不可以被类直接调用;同样可以通过类来获取静态成员(形式如Test::temp
)而不可通过某对象获取
再看一个更为实际的应用,我们想要确定Date
类到底创建了多少对象,可以做如下修改:
private:
...
static int obj_num; //声明静态变量obj_num用于存储对象个数
public:
static int get_counted(){
return obj_num;
}
Date(int year, int month, int day)
{
...
obj_num++; //每次析构函数执行一次便将obj_num递增一次
}
...
int Date::obj_num = 0; //静态变量一定要在外围声明!!!
int main()
{...
这是静态成员的一种常见用法
-
常量成员函数
对于刚才Data
类的例子,我们可以定义get()
取值,这里可以使用常量成员函数,如
...
int get_day() const { return this->day;}
通过使用const
标识,指明了这个函数不会修改Data
的状态,像是下面这个函数就是有误的
int get_day() const {
return this->day++; //错误:在const函数内企图修改成员值
}
-
友元
友元可以是函数,也可以是类,所以有友元函数,友元类的称呼。友元函数是类定义中由关键字friend
修饰的非成员函数。友元可以是普通函数,也可以是其他类的成员函数,但是在友元函数体中可以访问到类的私有成员和保护成员。下面看一个简单的例子
class TestCase1
{
private:
int t;
public:
TestCase1(int t=1)
{
this->t = t;
}
friend TestCase2 invite(TestCase1 obj);
};
class TestCase2
{
private:
char s;
public:
TestCase2(char s='a')
{
this->s = s;
}
get_ch()
{
return this->s;
}
};
TestCase2 invite(TestCase1 obj)
{
return obj.t<10? TestCase2():TestCase2('b');
}
int main()
{
TestCase1 t1();
TestCase1 t2(12);
TestCase2 t3=invite(t1);
TestCase2 t4=invite(t2);
cout<<t3.get_ch();
cout<<t4.get_ch();
}
···
###类作用域