7.1定义抽象数据类型
7.1.1常量成员函数
this指针:this是额外的隐式参数,它本质上是一个常量指针,与指向常量的指针不同。前者相当于int *const,是表示指针指向的地址不能改变,而指向常量的指针不一样,代表指向的地址的值不能变,相当于const int *。
const成员函数:const成员函数的书写一般是在函数后面加上关键字const,代表着这个函数不会修改对象的值,理解就是讲this 变成了指向常量的指针。
class Person{
public:
string pname;
string paddress;
Person();
Person(string pname,string paddress);
//常量成员函数,意味着这个函数不会修改对象的值
string getName() const;
//情况1
string getAddress() const{return this->pname};
//情况2
string getAddress_error(){return this->paddress};
};
string Person::getName() const{
return this->pname;
};
int main(){
const Person person("Mike","US");
person.getAddress_error();//错误 相当于const string name="Mike"; string *const oname=&name;
person.getAddress();//正确 相当于const string name="Mike"; const string *oname=&name;
}
当然const还可以用来修饰类里面里面不会改变的常量,同Person
//新增函数输出
//const意味着这个函数不能修改person的值,而且只能将它的值拷贝给常量而非非常量
ostream& print(ostream&os,const Person&person){
os>>person.getAddress();//正确
//os>>person.getAddress_error();//错误
return os;
}
另外这里要区分的点是const int function()是代表返回值为常量,而非常量成员函数
7.1.2构造函数
构造函数的作用只要用来初始化类的成员,当类没有声明任何构造函数的时候,系统就会默认提供一个构造函数。
构造函数有着默认的初始化列表,可以换一种写法。
class Person{
public:
string pname;
string paddrss;
//构造函数的初始化列表
Person(string pname,string paddress):pname(pname),paddress(paddress){}
};
7.2 访问控制与封装
public字段表示在整个程序都可以被访问。
private字段表示只可以被类的成员函数访问。
Protected:保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的。
struct和class的区别的默认的访问权限不同,struct的默认访问权限是public,而class的默认访问权限是private
7.2.1友元函数
这里就是让外部的函数可以访问类的私有变量,比如输入函数,在函数面前加friend函数即可。
优点:可以灵活地实现需要访问若干类的私有或受保护成员才能完成的任务,便于与其他不支持类的语言进行混合编程;通过使用友元函数重载可以更自然第使用C++语言的I/O流库。 缺点:一个类将对非公有成员的访问权授予其他的函数或类,会破坏该类的封装性,降低该类的可靠性和可维护性。
class Person{
friend iostream&is(iostream&is,Person p){
is>>p.name;
return is;
}
public:
Person(string name):name(name){}
private:
string name;
};
7.2.2重载操作符operator
//在类里面的话,尽量写成inline函数
class Person{
public:
Person(string name):name(name){}
string name;
inline bool operator==(const Person&p) const{
return this.name==p.name;
}
};
//在类外面的话
inline bool operator==(const Person&p1,const Person&p2) const{
return p1.name==p2.name;
}
//调用方式
int main(){
Person p1;
Person p2;
if(p1==p2){
cout<<1<<endl;
}
return 0
}
7.3类的其它特性
Tips1:内联函数一般在类的外部定义的地方说明inline,这样子有利于类的理解。
Tips2:类的成员函数可以重载,比如下面的get。
Tips3:使用mutable可以定义可变数据成员,这样子在常量成员函数里面也可以修改它的值。
只有内置类型和string可以依赖拷贝和赋值操作的默认版本。
我的理解拷贝的意思就是传引用,而赋值的意思就是重新生成一个对象,再去赋值。
class Screen{
public:
//为size_type显示别名
//这种必须先定义再使用
typedef string::size_type pos;
Screen(const Screen&s);//拷贝
Screen operator=(const Screen&s);//赋值
void cntCount()const{
cnt++;
}
Screen()=default;//默认构造函数
Screen(pos height,pos width,char c):height(height),width(width),contents(height*width,c){}
char get()const{
return contents[cursor];
}//隐式内联
inline char get(post ht,post wd) const;//显示内联
Screen&move(post r,post c);//在外部定义的地方说明内联
private:
pos cursor=0;
pos height=0,width=0;
string contents
//可变数据成员
mutable size_t cnt=0;
};
inline
Screen&::Screen m=move(post r,post c){
post row=r*width;
cursor=row+c;
return *this;
}
Tip4:const成员函数发挥的this指针是指向常量的指针,这样子再去调用set等修改函数会报错。
class Screen{
Screen& display() const{
return *this;
}
void set(int num);
};
//myscreen.display().set(0)会报错
Tip5:友元函数再探,比如类A和B,B的函数需要访问A,在A中声明友元类B,如果只想要B的某一个函数访问A,那么在A中声明这个函数就行。友元函数不具有传递性,B的友元函数无法访问A。
class A{
public:
friend class B;
friend class B::Set();
private:
int c;
};
class B{
void set();
friend void get();//这个函数无法访问A的私有成员
};
7.4类的作用域
对于类外面的定义,需要使用类::去访问,比如在类外部的函数的定义。
class T{
public:
typedef string::size_type pos;
string A;
pos getL() const;
};
T::pos T::getL() const{ //这里的pos要通过T::访问
return A.length();
}
7.4.1名字查找与类的作用域
- 一般名字查找先从所在的块查找,只考虑在名字之前出现过得声明
- 没找到则去找外层的作用域
- 最终没有匹配到的声明,则报错
对于定义在类内部的成员函数来说,首先编译成员的声明,直到类全部可见的话编译函数体。
7.5构造函数再探
7.5.1构造函数初始值列表
构造函数的初始值有时候必不可少,比如const和引用的话,必须将其初始化。下面举一个形象的例子
(这里的话讲个自己的理解,对于任何一个数比如int a
这样子的话就代表把他初始化了,默认值为0,之后再有语句a=1
这样子就是赋值了。int a=5
或者int a(5)
这样子的话都是可以的)
class Test{
public:
//错误写法
Test(int val){
x=val;//正确
y=val;//错误 y没有初始化
z=val;//错误 z没有被初始化
}
//正确写法
Test(int val):x(val),y(val),z(val){}
private:
int x;
const int y;
int&z;
};
当使用正确写法的初始化的操作的话,初始化的顺序并不是看 Test(int val):x(val),y(val),z(val)这一段文字,而是看声明顺序,x,y,z。即使构造函数是Test(int val):y(val),x(val),z(val),这也意味着先初始化x然后再是y和z。
7.5.2默认构造函数的作用
委托构造函数,先执行受委托的构造函数的主体,在执行委托构造函数的主体。
class sale{
int a;
int b;
sale(int a,int b):a(a),b(b){}
//委托构造函数
sale(int a):sale(a,0){cout<<"test"<<endl;}
};
默认构造函数一般在不使用初始值定义一个非静态变量 int a,A a
等等显得十分重要,当一个类声明了构造函数的话,那么就没有构造函数。
class B{
int b;
//B()=default; //加了这个默认构造函数那么才算成功
B(int c):b(c){}
};
struct A{
B b;
};
int main(){
A a; //会报错,因为b没有默认构造函数,因此无法成功
}
7.5.3隐式的类类型转换
比如对于一个类它的构造函数有只包含一个参数的很容易发生隐式的类类型转换。我感觉这个很神奇
比如对于一个类
class A{
int a;
A(int a):a(a){}//如果写成explicit A...,那么A a=1就是错误的
};
//神奇的地方出现了,常见的初始化A的对象的方法就是A a(1);
int main(){
A a(1);
A a=1;//这样子也对,隐式类型转换
}
如果你不喜欢这样子的隐式的类类型转换,你只需要在构造函数加上一个explicit关键字即可. 接下看来还有一个有趣的事情,编译器只会自动的执行一次转换,这里特别要注意string类型
int B(string c);
int main(){
B("11111");//这里其实默认执行了一次隐藏类类型转换
}
//下面结合这个和A来看
//假设把A中的int类型变为string类型
class A{
public:
string a;
A(string a):a(a){}//如果写成explicit A...,那么A a=1就是错误的
};
//神奇的地方出现了,常见的初始化A的对象的方法就是A a(1);
int main(){
A a("11111");//这样子正确 执行初始化,执行了一次隐藏类类型转换
A b="111111";//这样字是错误的,“111111”转sting一次转换,string转成A的对象类型有一次
A c=string("11111111");//正确,显示的强制转换了一次
}
7.5.4constexpr函数
- 函数返回值类型和所有的形参都是字面值类型
- 函数体只有一条return语句
- 被隐式的转换为内联函数
//constexpr允许返回非常量
constexpr size_t scale(size_t cnt){return 2*cnt}
scale(2);//传入的是常量,因此输出常量
int i=2;
scale(i);//非常量,输出的是非常量
7.6 类的静态成员
主要用于一个成员不仅仅在这个类使用,还可以在其他类使用,并不属于这个类的某一个对象。比如银行的利率。
class A{
static int b;//在类里面定义是错误的,比如static int b=100;错误的
const static int c=100;//正确 加了const关键字
int a;
A(int a):a(a){}
}
//在类外定义
int A::b=100;