本篇通过类来讨论C++中封装的问题。
不是全面讲述封装,是摘录一些我学习过滤后觉得值得注意的一些点。
1.访问控制
public,private,protected
2.构造函数。
构造函数需要考虑多种情况,
(1)默认(无参或有默认参数)构造函数,(若不定义则由系统自动生成)
这个必须有生成数组的时候会用到
(2)有参构造函数
(3)拷贝构造函数(此种情况通过类对象a来初始化新建对象),有深拷贝和浅拷贝的问题。 (当类对象中有通过指针new的内存时,要执行深拷贝,否则两个指针指向同一块内存,会在析构时重复析构而出现错误)
MyString::MyString(const MyString & other)
{
int len = strlen(other._str);
this->_str = new char[len+1];
//可以加判断是否申请内存成功
if(this->_str == NULL)
{
//return ;
}
strcpy(this->_str,other._str); //拷贝
}
3.拷贝构造可与赋值运算符重载对比,赋值运算符重载中需要考虑将对象本身赋给自己的情况,而拷贝构造不需要。
MyString & MyString::operator=(const MyString & another)
{
if(this == &another)
return *this;
else
{
delete []this->_str;
int len = strlen(another._str);
this->_str = new char[len+1];
strcpy(this->_str,another._str);
return *this;
}
}
strcpy(this->_str,another._str);
赋值运算符重载返回的是引用,这样可以实现连=操作。
要先释放原先的内存,重新申请相应大小并拷贝内容 。
4.加法运算符重载
计算总内存长度,释放原有内存,申请相加长度后的内存,清零,赋值
MyString MyString::operator+(const MyString & other)
{
int len = strlen(this->_str) + strlen(other._str);
MyString str;
delete []str._str;
str._str = new char[len+1];
memset(str._str,0,len+1);
strcat(str._str,this->_str);
strcat(str._str,other._str);
return str;
}
strcat(str._str,other._str); //相加
5.const 修饰类的成员变量,表示成员常量,不能被修改,同时它只能在初始化列表中赋值
class A
{
public:
A():iValue(199){}
private:
const int iValue;
6.常成员函数
const 修饰函数的意义
承诺在本函数内部不会修改类内的数据成员,不会调用其它非 const 成员函数
类体外定义的 const 成员函数,在定义和声明处都需要 const 修饰符 。 加在末尾
const 构成函数重载
class A
{
public:
A():x(199),y(299){}
void dis() const //const 对象调用时,优先调用
{
//input(); 不能调用 非 const 函数,因为本函数不会修改,无法保证所调的函数也不会修改
cout<<"x "<<x<<endl;
cout<<"y "<<y<<endl;
//y =200; const 修饰函数表示承诺不对数据成员修改。
}
void dis() //此时构成重载,非 const 对象时,优先调用。
{
y = 200;
input();
cout<<"x "<<x<<endl;
cout<<"y "<<y<<endl;
}
7.静态成员
静态函数只能访问静态成员
调用
类名::函数调用
类对象.函数调用
静态成员函数属于类,而不属于对象,没有 this 指针
static const 成员
如果一个类的成员,既要实现共享,又要实现不可改变,那就用 static const 修饰。
修饰成员函数,格式并无二异,修饰数据成员。必须要类内部初始化。
class A
{
public:
static const void dis()
{
cout<<i<<endl;
}
private:
const static int i = 100;
};
8.定义指向类成员或类成员函数的指针
指向类数据成员的指针
定义
<数据类型><类名>::<指针名>
赋值&初始化
<数据类型><类名>::<指针名>[=&<类名>::<非静态数据成员>]
指向非静态数据成员的指针在定义时必须和类相关联,在使用时必须和具体的对象
关联。
在使用这类指针时,需要首先指定类的一个对象,然后,通过对象来引用指针所指向的成员。
<类对象名>.<指向非静态数据成员的指针>
<类对象指针>-><指向非静态数据成员的指针>
string Student::*ps = &Student::name;
cout<<s.*ps<<endl;
cout<<s2.*ps<<endl;
Student *pp = new Student("wangwu",1003);
cout<<pp->*ps<<endl;
指向类成员函数的指针
定义一个指向非静态成员函数的指针必须在三个方面与其指向的成员函数保持一致:参数
列表要相同、返回类型要相同、所属的类型要相同
定义
<数据类型>(<类名>::<指针名>)(<参数列表>)
赋值&初始化
<数据类型>(<类名>::<指针名>)(<参数列表>)[=&<类名>::<非静态成员函数>]
void (Student::*pf)() = & Student::dis;
(s.*pf)();
(s2.*pf)();
(ps->*pf)()
实例:用指向类成员函数的指针,实现更加隐蔽的接口
class Widget
{
public:
Widget()
{
fptr[0] = &f;
fptr[1] = &g;
fptr[2] = &h;
fptr[3] = &i;
}
void select(int idx, int val)
{
if(idx<0 || idx>cnt) return;
(this->*fptr[idx])(val);
}
int count()
{
return cnt;
}
private:
void f(int val){cout<<"void f() "<<val<<endl;}
void g(int val){cout<<"void g() "<<val<<endl;}
void h(int val){cout<<"void h() "<<val<<endl;}
void i(int val){cout<<"void i() "<<val<<endl;}
enum{ cnt = 4};
void (Widget::*fptr[cnt])(int);
};
int main()
{
Widget w;
for(int i=0; i<w.count(); i++)
{
w.select(i,1);
}
return 0;
}
//指向类静态成员的指针
int *p = & A::data;
cout<<*p<<endl;
void (*pfunc)() = &A::dis;
9.前向声明
前向声明,是一种不完全型(forward declaration)声明,即只需提供类名(无需提供类实现)即可。正因为是(incomplete type)功能也很有限:
(1)不能定义类的对象。
(2)可以用于定义指向这个类型的指针或引用。
(3)用于声明(不是定义),使用该类型作为形参类型或者函数的返回值类型
class Point; //前向声明
class ManagerPoint
{
public:
double Distance(Point &a, Point &b);
};
class Point
{
public:
Point(double xx, double yy)
{
x = xx;
y = yy;
}
void Getxy();
friend double ManagerPoint::Distance(Point &a, Point &b);
};
声明为谁的友元,就可以通过谁的对象访问谁的私有成员
这里ManagerPoint::Distance声明为Point的友元,就能通过Point的对象,访问Point的私有成员
10.友元类
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包
括私有成员和保护成员)。
当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。定义友元类的语句格式如下:
friend class 类名;
class A
{
public:
friend class B;
};
类 B 是类 A 的友元类,类 B 的所有成员函数都是类 A 的友元函数,能存取类 A 的私有成员和保护成员
11.用类型转换构造函数进行类型转换
explicit Point3D(Point2D &p) //注:explicit 是个仅用于声明的关键字
{
this->_x = p._x;
this->_y = p._y;
this->_z = 0;
}
12.用类型转换操作符进行转换
class 源类{
operator 转化目标类(void)
{
//根据需求完成从源类型到目标类型的转换
}
}
Point2D::operator Point3D()
{
return Point3D(_x,_y,0);
}
13.函数操作符(())—仿函数(把类对象像函数名一样使用)
实现一个operator(),这个类就有了类似函数的行为
class 类名
{
返值类型 operator()(参数类型)
函数体
}
#include <iostream>
#include <vector>
using namespace std;
class Pow
{
public:
int operator()(int i)
{
return i*i;
}
double operator ()(double d)
{
return d*d;
}
};
int main()
{
Pow pow;
int i = pow(4); //pow.opreator()(4);
double d = pow(5.5);
cout<<i<<endl;
cout<<d<<endl;
return 0;
}
14.代理类,
通过向客户提供只知道类的 public 接口的代理类,就可以使客户能够使用类的服务,而无法访问类的实现细节。
#include <iostream>
using namespace std;
class Implementation
{
public:
Implementation(int v):value(v)
{}
void setValue(int v)
{
value = v;
}
int getValue() const
{
return value;
}
private:
int value;
};
class Delegate
{
public:
Delegate(int v):pi(new Implementation(v))
{}
void setValue(int v)
{
pi->setValue(v);
}
int getValue() const
{
return pi->getValue();
}
private:
Implementation * pi;
};
int main()
{
Delegate d(5);
d.setValue(100);
cout<<d.getValue()<<endl;
return 0;
}
特别关注此处pi对象的申请内存及初始化!
Delegate(int v):pi(new Implementation(v))