C++学习笔记
----类
文章目录
一.类的概念
- 概念
类是描述实体的一种数据类型,将实体的属性和行为进行抽象后封装。 - 声明:
和结构所不同的是,类在声明的时候,要定义类的数据成员和类的成员函数。
class 类名
{
private:
数据类型1 数据成员名1;
数据类型2 数据成员名2;
...
public:
类型1 成员函数名1(参数列表);
类型2 成员函数名2(参数列表);
...
};
类型名 类名::函数名(参数表)
{
函数体
}
>类的数据成员不能同名,但可以与程序中的其它变量同名,不同类的数据成员也可以同名。
>类在声明时并不会分配内存,只有定义后才会分配内存。
>类的数据成员不能加auto, register, extern修饰,但可以用static, const修饰。
>类的数据成员在声明时,不允许用表达式进行初始化。
>可以定义除本类对象以外的所有基本和用户自定义类型的数据成员,包括其它类的对象,也可以定义本类类对象的指针数据成员。
>数据成员体现的是实体的属性,成员函数中用到的局部变量(如循环变量等),不应该定义为类的数据成员。
- 成员函数的定义也可以直接置于定义中的花括号内。此时,成员函数将被缺省为按内联方式处理。
- 访问控制方式保护类中数据成员不被其它类或其它函数破坏:
private(私有): 除了该类的成员函数外,其它函数无法访问数据成员;
protected(保护): 该类的成员函数以及该类的派生类的函数可访问,其它函数无法访问;
public(公布): 这种成员是该类对外的界面,允许程序的所有函数访问。
如果不做声明,则相应的成员函数的访问控制属性缺省为private。
- 定义
类名 类对象名1,类对象名2,...;
>为一个类对象所分配的内存是这个对象所属的类类型中所有非静态数据成员所占的内存之和(还要考虑内存对齐的因素–大部分处理器并不是按字节而是以4的倍数个字节为单位来存取内存,这样类对象每个数据成员的首地址是4或8的倍数,不足的要补齐)
>类的成员函数的地址是全局已知的,对象的内存空间里无需保存成员函数的地址,对类对象而言类的成员函数就像全局函数一样。
- 引用
(1)用对象名来访问对象数据成员和调用成员函数:
对象名.数据成员名
对象名.成员函数名(参数表)
(2)用对象指针来访问对象数据和调用成员函数:
对象指针名->数据成员名
对象指针名->成员函数名(参数表)
*对象指针名.数据成员名
对象指针名.成员函数名(参数表)
- 案例
下面代码声明了一个点类:
class Point
{
public:
int GetX(); //在函数外访问私有的xp的方法
int GetY(); //在函数外访问私有的yp的方法
void setXY(int x, int y); //设置xp、yp的值
Point(); //构造函数
~Point(); //析构函数
private:
int xp;
int yp;
};
Point::Point()
{
xp = 0;
yp = 0;
}
int Point::GetX()
{
return this -> xp;
}
int Point::GetY()
{
return this -> yp;
}
void Point::SetPoint(int px, int py)
{
this -> xp = px; //或者x = px;
this -> yp = py; //或者y = py;
}
Point::~Point()
{
xp = 0;
yp = 0;
cout << "point clear" << endl;
}
也可以改为:
class Point
{
public:
int GetX() //在函数外访问私有的xp的方法
{
return this -> xp;
}
int GetY() //在函数外访问私有的yp的方法
{
return this -> yp;
}
void setXY(int x, int y) //设置xp、yp的值
{
this -> xp = px; //或者x = px;
this -> yp = py; //或者y = py;
}
Point() //构造函数
{
xp = 0;
yp = 0;
}
~Point() //析构函数
{
xp = 0;
yp = 0;
cout << "point clear" << endl;
}
private:
int xp;
int yp;
};
由于xp和yp被设置为private,无法在mian函数中直接通过.xp和.yp访问,所以需要设置get方法。
二.this指针
类中的每个成员函数都会有一个隐藏的指向本类对象的称为this的指针形参。它指向当前对象,通过它可以访问当前对象的所有成员,可以用来给成员赋值。
this指针通过使用->来访问成员变量或者成员函数。
> this是const指针,它的值不能被修改的。
>this只能在成员函数内部使用,不能在其他地方使用。
>只有当对象被创建后this才有意义,因此不能在static成员函数中使用。
int GetX()
{
return this -> xp;
}
int GetY()
{
return this -> yp;
}
三.类对象的初始化
由于类的数据成员通常设置为私有访问属性,类对象的初始化不能够采用初始化列表的方式,也不能采用直接赋值的方式。通常需要在类中专门定义一个对类对象初始化的具有公有访问属性的成员函数。
- 显式初始化
class Number
{
private:
int num; //int num = 0;不被允许
public:
void init(int n)
{
num = n;
}
};
int main()
{
Number numb;
numb.init(0); //num = 0
return 0;
}
- 构造函数初始化
- 拷贝构造函数(赋值(复制)构造函数)初始化
四.构造函数
在创建对象的同时会发生一个对构造函数的自动隐式地调用,完成对象的初始化工作。
class 类名
{
private:
...
public:
...
类名(参数列表);
};
类名::类名(参数列表)
{
...
}
- 无参构造函数(默认构造函数)初始化
class Point
{
public:
...
void setXY(int x, int y);
Point();
private:
int xp;
int yp;
};
Point::Point()
{
xp = 0;
yp = 0;
cout << "Point created." << endl;
}
int main()
{
Point p;
...
}
- 带参数的构造函数初始化
class Point
{
public:
...
Point(int x, int y);
private:
int xp;
int yp;
};
Point::Point(int x, int y)
{
xp = x;
yp = y;
cout << "Point created." << endl;
}
int main()
{
int x, y;
cin >> x >> y;
Point p(x, y);
...
}
- 使用初始化列表来初始化字段
class Point
{
public:
...
Point(int x, int y);
private:
int xp;
int yp;
};
Point::Point(int x, int y):xp(x),yp(y)
{
cout << "Point created." << endl;
}
int main()
{
int x, y;
cin >> x >> y;
Point p(x, y);
...
}
五.拷贝构造函数
拷贝构造函数是形参为本类对象常引用的构造函数,其原型为:
类名(const 类名 &形参名)
- 常用于:
- 通过使用另一个同类型的对象来初始化新创建的对象。
- 复制对象把它作为参数传递给函数。
- 复制对象,并从函数返回这个对象。
- 拷贝构造函数不能直接调用。会自动隐式地调用拷贝构造函数的情况:
- 在定义对象时,使用一个已定义的对象来初始化另一个同相同类的类对象。
- 类对象作为函数的形参按传值方式调用,实参对象和形参对象结合时。
- 函数返回类型为类对象,从函数返回一个对象值时。
- 如果没有定义拷贝构造函数,C++编译程序会为每个类提供一个缺省的拷贝构造函数。如果定义了,则不会提供。缺省的拷贝构造函数会将源对象中的数据成员值逐个拷贝到目标对象相应的数据成员。
- 如果类对象有指向动态分配的堆内存的指针数据成员,则必须定义拷贝构造函数实现指针的深拷贝,也即让目标对象和源对象指针指向不同的堆内存然后再把源对象堆内存的数据拷贝到目标对象的堆内存,而不是直接把源对象的指针值赋值给目标对象的指针(又称浅拷贝),使它们具有相同的地址。如果两个对象的指针数据成员指向相同的堆内存,当某个对象析构时会释放其堆内存,而另一个对象析构时就会再去尝试释放同一块内存而出错。
- 代码实现
用对象1 初始化对象2
#include <iostream>
using namespace std;
class Line
{
public:
int getLength();
Line(int len);
Line(const Line &l);
~Line();
private:
int *ptr;
};
Line::Line(int len)
{
ptr = new int;
*ptr = len;
}
Line::Line(const Line &l)
{
ptr = new int;
*ptr = *l.ptr;
}
Line::~Line()
{
delete ptr;
}
int Line::getLength()
{
return *ptr;
}
void display(Line l)
{
cout << "line 大小 : " << l.getLength() <<endl;
}
int main()
{
Line line1(10);
Line line2 = line1; //拷贝构造,Line line2(line1);
display(line1);
display(line2);
return 0;
}

函数的类类型实参初始化形参
#include <iostream>
using namespace std;
class Line
{
public:
int getLength();
Line(int len);
Line(const Line &l);
~Line();
private:
int *ptr;
};
Line::Line(int len)
{
ptr = new int;
*ptr = len;
}
Line::Line(const Line &l)
{
ptr = new int;
*ptr = *l.ptr;
}
Line::~Line()
{
cout << "delete" << endl;
delete ptr;
}
int Line::getLength()
{
return *ptr;
}
void display(Line l) //函数的类类型实参初始化形参
{
cout << "line 大小 : " << l.getLength() <<endl;
}
int main()
{
Line line(10);
display(line);
return 0;
}

函数返回类类型
#include <iostream>
using namespace std;
class Line
{
public:
int getLength();
Line(int len = 0); //缺省
Line(const Line &l);
~Line();
private:
int *ptr;
};
Line::Line(int len)
{
ptr = new int; //一定要有
*ptr = len;
}
Line::Line(const Line &l)
{
ptr = new int;
*ptr = *l.ptr;
}
Line::~Line()
{
delete ptr;
}
int Line::getLength()
{
return *ptr;
}
void display(Line l)
{
cout << "line 大小 : " << l.getLength() <<endl;
}
Line copy() //函数返回类类型
{
Line line1(10);
return line1;
}
int main()
{
Line line2;
line2 = copy();
display(line2);
return 0;
}

其中,在函数返回类类型中,可以使用引用:
无引用无拷贝
#include <iostream>
using namespace std;
class Line
{
public:
int getLength();
Line(int len);
~Line();
private:
int *ptr;
};
Line::Line(int len)
{
ptr = new int;
*ptr = len;
}
Line::~Line()
{
cout << "delete" << endl;
delete ptr;
}
int Line::getLength()
{
return *ptr;
}
void display(Line l)
{
cout << "line 大小 : " << l.getLength() <<endl;
}
int main()
{
Line line(10);
display(line);
return 0;
}

引用无拷贝
#include <iostream>
using namespace std;
class Line
{
public:
int getLength();
Line(int len);
~Line();
private:
int *ptr;
};
Line::Line(int len)
{
ptr = new int;
*ptr = len;
}
Line::~Line()
{
cout << "delete" << endl;
delete ptr;
}
int Line::getLength()
{
return *ptr;
}
void display(Line &l)
{
cout << "line 大小 : " << l.getLength() <<endl;
}
int main()
{
Line line(10);
display(line);
return 0;
}

六.析构函数
析构函数是用于对包含有指向动态分配的堆内存的指针、文件句柄等数据成员的类对象在撤消时释放其所占用的系统资源的一种自动隐式调用的成员函数,带隐藏的this指针形参。
class 类名
{
private:
...
public:
...
~类名();
};
类名::~类名()
{
...
}
- 析构函数没有参数,因此不可以重载,每个类只有一个析构函数。
- 当类对象按作用域规则撤消时会自动隐式调用该类对象所属类的析构函数。也可以用对象名直接调用析构函数,但这样会使该对象析构两次。
- 析构函数与构造函数一样不能有返回类型(包括void),也即析构函数的函数体内不能有return 返回值的语句,但是可以有无返回值的return。
- 用delete p运算符来撤消p指向的一个用动态创建的类对象时,会自动调用该类的析构函数。撤销对象数组时要带[],即delete [] p;此时析构函数调用次数与数组中对象个数相同。
- 析构函数调用的顺序与对象被构造的顺序相反,即最先构造的对象将被最后析构。
- 如果没有定义析构函数,C++编译程序会为每个类提供一个不带参数且函数体为空的析构函数。如果定义了,则不会提供。
class CSieve
{
private:
char *p_sieve;
unsigned long num;
public:
CSieve(unsigned long n);
void printPrime();
~CSieve();
};
CSieve::CSieve(unsigned long n)
{
num = n;
p_sieve = new char[num];
}
...
CSieve::~CSieve()
{
num = 0;
delete [] p_sieve;
}
七.函数重载
函数重载是指对于具有不同参数数目、不同参数类型或者参数个数和参数类型都不同的一组功能相关的函数使用相同的函数名。
- C++不是根据函数名,而是根据函数参数的个数和类型,以及参数类型的顺序来作为调用函数的唯一标识。
- 仅仅是返回类型不同的两个函数、或参数个数一样,而参数类型一个为类型,一个为引用的两个函数不能构成重载函数。
#include<iostream>
using namespace std;
class Point
{
public:
Point();
Point(int x1, int y1);
void printPoint();
private:
int xp;
int yp;
};
Point::Point(int x1, int y1)
{
xp = x1;
yp = y1;
}
Point::Point()
{
xp = 0;
yp = 0;
}
void Point::printPoint()
{
cout << "x = " << xp << endl << "y = " << yp << endl;
}
int main()
{
int x, y;
cin >> x >> y;
Point p1;
Point p2(x, y);
p1.printPoint();
p2.printPoint();
return 0;
}

八.内联函数
内联函数就是在函数头以关键字inline开始的函数。在程序编译时,内联函数的任何调用都被替换成该函数函数体内的各个语句,内联函数调用时不必保存断点地址等现场环境,不使用栈空间,没有函数调用的开销。
inline 类型 函数名(参数表)
{
...
}
- 若内联函数中出现了循环语句、开关语句、递归函数调用语句,则编译程序不对其使用内联功能,将其作为一般的函数来处理。
- 在类说明内部定义的函数都是内联函数。
#include <iostream>
using namespace std;
inline int Max(int x, int y)
{
return (x > y)? y : x;
}
int main( )
{
cout << "Max(1,2): " << Max(1,2) << endl;
cout << "Max(1,1): " << Max(1,1) << endl;
cout << "Max(2,1): " << Max(2,1) << endl;
return 0;
}

九.带缺省参数值的函数
可以通过赋值运算对函数参数表中的一个到多个参数设定缺省值。
int myFunction(int x = 1, int y = 2, int z = 3); //全缺省
...
myFunction(3); //x = 3,y和z取缺省值2和3
myFunction(3, 5); //x = 3, y = 5, z取缺省值3
- 如果既有函数定义又有函数说明语句时,则缺省参数只能出现在函数定义或函数说明两个语句其中之一,不能同时出现。
int myFunction(int x = 1);
int myFunction(int x) //不可以int x = 1
{
...
}
- 缺省参数应从右至左逐渐定义,不允许在参数中间对参数使用缺省值。当调用函数时,只能向左匹配参数,若某个参数省略,则其后参数都应该省略而采用缺省值。
int myFunction(int x, int y = 2, int z = 3); //半缺省
int myFunction(int x, iny y = 2, int z); //不可以
十.复合类及其类对象的初始化
如果一个类A的数据成员为另外一个类B的类对象,则类A为复合类。
- 由于子对象的数据成员为私有的,不能直接在复合类的构造函数中用赋值语句对子对象的数据成员初始化。
(1)适用于子对象类有构造函数的情况。
复合类类名::复合类类名(type1 arg1, type2 arg2, ..., 其他参数):子对象1(arg1), 子对象2(arg2), ...
{
用其他参数初始化复合类非对象数据成员
}
class Point
{
public:
...
Point(int x, int y){ xp=x; yp=y; }
private:
int xp;
int yp;
};
class Circle
{
public:
...
Circle(int x1, int y1, int r1):p(x1, y1)
{
r = r1;
}
private:
Point p;
int r;
};
int main()
{
int x1, y1, r1;
cin >> x1 >> y1 >> r1;
Circle circle(x1, y1, r1);
...
}
或者:
public:
Circle(int x1, int y1, int r1);
...
Circle::Circle(int x1, int y1, int r1):p(x1, y1)
{
r = r1;
}
(2)适用于子对象类未定义构造函数。
复合类类名::复合类类名(type1 arg1, type2 arg2, ..., 其他参数)
{
子对象1.成员函数1(arg1);
子对象2.成员函数2(arg2);
用其他参数初始化复合类非对象数据成员
}
#include<iostream>
using namespace std;
class Point
{
public:
...
void Setxp(int x){ xp=x; }
void Setyp(int y){ yp=y; }
private:
int xp;
int yp;
};
class Circle
{
public:
...
Circle(int x, int y, int r1);
private:
Point p;
int r;
};
Circle::Circle(int x, int y, int r1)
{
p.Setxp(x);
p.Setyp(y);
r = r1;
}
int main()
{
int x, y, r1;
cin >> x >> y >> r1;
Circle circle(x, y, r1);
...
}
- 复合类拷贝构造函数
复合类对象构造时,先按其子对象在复合类定义的顺序(而不是在初始化列表出现的顺序),调用子对象的构造函数初始化子对象,然后再执行复合类构造函数函数体对其非对象数据成员进行初始化。复合对象在撤销时,顺序正好与其创建的次序相反。
复合类类名::复合类类名(const 复合类类名 &r):子对象名(r.子对象名)
{
数据成员1 = r.数据成员1;
数据成员2 = r.数据成员2;
...
}
class Point
{
public:
...
Point(int x, int y){ xp=x; yp=y; }
private:
int xp;
int yp;
};
class Circle
{
public:
...
Circle(int x1, int y1, int r1):p(x1, y1)
{
r = r1;
}
Circle(const Circle &c):p(c.p)
{
r = c.r;
}
private:
Point p;
int r;
};
int main()
{
int x1, y1, r1;
cin >> x1 >> y1 >> r1;
Circle circle(x1, y1, r1);
Circle circle1(circle);
...
}
十一.静态数据成员
- 当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
static 数据类型 数据成员名;
- 静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部对它进行初始化。
数据类型 类名::数据成员名 = 值;
- 类的成员函数可以直接访问该类的静态数据成员。如果静态数据成员为public,则可以在类的外部用对象名访问静态数据成员,也可用类名访问:
类名::静态数据成员名
- 由于静态数据成员的特殊性,尽管不在构造函数对其初始化,但通常都需要分别在构造函数和析构函数中对其值进行更新。
- 代码实现+运行结果
#include <iostream>
using namespace std;
class Line
{
private:
int *len;
public:
static int count;
Line(int lens)
{
cout << "Created." << endl;
len = new int;
*len = lens;
count++;
}
~Line()
{
cout << "Deleted." << endl;
count--;
delete len;
}
};
int Line::count = 0;
int main()
{
Line l1(10);
Line l2(20);
cout << "count: " << Line::count << endl;
l1.~Line();
l2.~Line();
cout << "count: " << Line::count << endl;
return 0;
}

- 用途
(1)记录对象的总数等统计操作。在构造函数中对该静态数据成员加1,在析构函数里对该静态数据成员减1。
(2)表示对象共有的数据,如最大值、最小值;某个类的所有类对象共享的一块动态分配的内存等。
(3)作为一个标记,标记一些动作是否发生,比如:文件的打开状态,打印机的使用状态,等等。
(4)存储链表的第一个或者最后一个节点的内存地址。如链表的表头等。
十二.静态数据函数
- 静态成员函数没有this指针形参,也不能用const关键字修饰。
- 静态成员函数智能访问静态数据成员、全局变量和静态成员函数的参数,而不能访问描述该类各对象状态的非静态数据成员。
- 调用方式:
类名::静态成员函数名(参数表);
或对象名.静态成员函数名(参数表);
- 代码实现+运行结果
#include <iostream>
using namespace std;
class Line
{
private:
int *len;
public:
static int count;
Line(int lens)
{
cout << "Created." << endl;
len = new int;
*len = lens;
count++;
}
static int getCount()
{
return count;
}
~Line()
{
cout << "Deleted." << endl;
delete len;
}
};
int Line::count = 0;
int main()
{
cout << "count: " << Line::getCount() << endl;
Line l1(10);
Line l2(20);
cout << "count: " << Line::getCount() << endl;
return 0;
}

十三.友元
- 若在类A中声明函数B和类C为友元,则称B为A类的友元函数,C为A类的友元类。
- 声明:
friend 函数返回类型 友元函数名(类名 &对象名, ...);
friend 类名;
- 友元函数不是类的成员函数,不能被声明为const,也没有this指针,所以不能直接访问类的成员,但可以通过在友元函数的参数列表里设置类对象形参,然后用类对象名来直接访问该类的所有成员(包括公有和私有的数据成员和成
员函数)。
例如函数B和类C为A类的友元,则元函数B可以通过A的类对象形参在函数内访问A类的所有成员。类C中的所有成员函数也可以通过A的类对象访问A类的所有成员。 - 特点
- A类的成员函数f要通过B类对象的形参来访问B类的私有成员,此时需要把f声明为B类的友元,而且A类的声明必须在B类之前并且函数f的定义不能在A内,只能在A、B两个类之后。
- 友元的关系是单向的而不是双向的。
- 友元的关系不能传递。
- 一般并不把整个类声明为友元类,而只将确实有需要的成员函数声明为友元函数,这样更安全一些。
- 友元是以破坏类对象的封装性和信息隐蔽为代价的,所以不要过多地使用友元。只有在使用友元有助于数据共享、能使程序精炼,并能大大提高程序的效率时才考虑用友元。
- 友元函数
#include <iostream>
using namespace std;
class CWife;
class CHusband
{
private:
float salary;
public:
CHusband(float s) { salary = s; }
friend float getFamilyIncome(CHusband &h, CWife &w);
};
class CWife
{
private:
float salary;
public:
CWife(float s) { salary = s; }
friend float getFamilyIncome(CHusband &h, CWife &w);
};
float getFamilyIncome(CHusband &h, CWife &w)
{
return h.salary + w.salary;
}
int main()
{
float hv, wv;
cin >> hv >> wv;
CHusband husband(hv);
CWife wife(wv);
cout << getFamilyIncome(husband, wife) << endl;
return 0;
}

- 友元类
#include <iostream>
using namespace std;
class Line
{
private:
int line;
public:
void setLong(int l) { line = l; }
friend class Lines;
friend void getLine(Line &lin);
};
class Lines
{
public:
void createLine(int l, Line &lin)
{
lin.setLong(l);
cout << "cline=" << lin.line << endl;
}
};
void getLine(Line &lin)
{
cout << "gline=" << lin.line << endl;
}
int main()
{
Line line1;
Lines line2;
line1.setLong(10);
getLine(line1); //友元函数输出
line2.createLine(20, line1);
getLine(line1);
return 0;
}
