一、类
成员函数可以访问类的私有成员变量
类的封装:将成员变量定义为私有,将成员函数定义为公有,用户只能通过成员函数访问成员变量
定义类对象的方式调用成员函数:类对象+运算符(.)
定义类对象指针动态生成类对象调用成员函数:类对象指针+箭头运算符(->)
二、类成员初始化——构造函数、析构函数与拷贝构造函数
1.通过默认值初始化:int age = 0;
2.通过初始化列表(数据成员是公有的)
3.通过构造函数初始化:构造函数和类同名,没有返回类型
1.构造函数
构造函数在类对象生成时由系统自动调用
用参数初始化表的方式:
Person(int n, char *p):age(n),name(p){}
也可以写成:age{n},name{p}
new生成动态对象会调用构造函数,new的返回值为类对象指针
委托构造函数:一个构造函数在其初始化列表中调用了另一个构造函数
public:
Box(double lv, double wv, double hv): length{lv}, width{wv}, height{hv}{}
Box(double side):Box(side, side, side){}
2.析构函数
无参数,不能被重载
静态分配内存创建的类对象:在main函数结束时自动被释放,调用析构函数
动态分配内存创建的类对象:类对象在delete时被释放,调用析构函数
delete:先调用析构函数(如果是类对象),再回收动态分配的空间
delete []:先为每个数组元素调用析构函数(如果是类对象),再回收动态分配的空间
p = new A[10]; //动态生成10个类A的对象
delete p; //只为第一个类A的对象调用析构函数,然后释放动态空间(错误!)
delete []p; //为数组中每个类A的对象调用析构函数,再回收动态分配的空间
构造顺序:a, b, *p;析构顺序:*p, b, a
3.拷贝构造函数
Person(const Person & p){...}
使用场合:
Person a = b;
Person c(b);
void f(A var) {}
return a;
拷贝构造函数名与类名相同,形参只能是类对象的引用,不能重载
深拷贝
class A {
private:
int* p;
public:
A() { p = new int[2]; }
~A() { delete[]p; }
A(const A& a) {
p = new int[2]; //动态生成新的空间
for (int i = 0; i < 2; i++) {
p[i] = a.p[i]; //拷贝每个数值
}
}
};
练习:
#include <iostream>
using namespace std;
class A {
public:
A() { cout << 1; }
A(const A&) { cout << 2; }
~A() { cout << 3; }
};
A test() {
A a;
return a;
}
int main() {
A b;
A c = b;
test();
b = test();
return 0;
}
输出: 121233123333
#include <iostream>
using namespace std;
class A {
public:
A() { cout << 1; }
A(const A&) { cout << 2; }
~A() { cout << 3; }
};
A test(A& a) {
return a;
}
int main() {
A b;
A c = b;
test(b);
test(c);
return 0;
}
输出:12232333
三、类与staic
1.类的静态成员
static 成员属于整个类,由所有类对象共享,在类定义的时候生成并分配空间
定义和初始化必须在类外进行,定义时不再加static
使用类名+(::)访问:A::m = 2;
2.类的静态成员函数
静态成员函数属于整个类,所有类对象都可调用
通过类名+(::)直接调用:A::f1();
1.静态成员函数可以访问静态成员变量 | 静态成员变量和静态成员函数都属于整个类,在类定义的时候已经存在 |
2.静态成员函数不能访问非静态成员变量 | 非静态成员变量属于某个类对象,而静态成员函数属于整个类,无法确定访问的是哪个类对象的变量 |
3.非静态成员函数可以访问非静态成员变量 | 非静态成员变量和非静态成员函数同属一个类对象 |
4.非静态成员函数可以访问静态成员变量 | 静态成员变量被所有类对象共享 |
5.静态成员函数不能调用非静态成员函数 | 非静态成员函数可能访问了非静态成员变量 |
类静态成员的应用:创建只有一个实例的类
四、类与const
1.类的常量数据成员:const int m;
2.类的常量成员函数——const放在函数尾部:常量成员函数不能修改类的非static成员变量
class A {
public:
int m;
int n;
void f() const;
};
void A::f() const {
m = 10; //ERROR,不能对m进行更改!
cout << a.m;
}
3.返回值为常量的成员函数——const放在函数头部:函数的返回值不能更改
class A {
public:
int m;
int n;
const int& f() { return m; }
};
void main() {
A a;
a.f()++; //ERROR! 函数返回值不能修改
cout << a.m;
}
五、this指针
非静态成员函数返回的是对象本身或对象地址
class A {
public:
int m;
A* f(){return this;}//返回对象地址
A f1(){return *this;}//返回对象本身
};
六、指向成员的指针
指向指针:<类型名> <类名>::*<指针变量名>;
指向函数:<类型名> (<类名>::*<函数指针名>)(形参表);
#include<iostream>
using namespace std;
class X {
public:
void f(int);
int a;
};
void X::f(int x)
{
a = x;
}
void main() {
int X::* pmi = &X::a;//指向类成员变量的指针
void (X:: * pmf)(int) = &X::f;//指向类成员函数的指针
X objx;
objx.*pmi = 10;
cout << objx.a << endl;
(objx.*pmf)(5);
cout << objx.a << endl;
}
七、类的友元
允许一些特殊的函数或者类访问其他类的私有成员,被称为友元。
函数
1.将普通函数声明为类的友元函数,此函数就可以访问类的私有成员
class A {
private:
int m;
public:
friend void f();
};
void f() {//f是普通函数,不属于类A
A a;
a.m = 2;
}
2.友元成员函数
将类B的成员函数声明为类A的友元函数,此函数就可以访问类A的私有成员
//必须先对A进行声明,否则类B里面的f(A &a)报编译错误
class A;
//必须将先定义的类(类B)的成员函数作为后定义类(类A)的友元函数,调换顺序会出现语法错误
class B {
public:
void f(A& a);
};
class A {
private:
int m;
public:
friend void B::f(A& a);
};
//必须将类成员函数f的函数体放在类A定义的后面,因为函数体中a.m = 2访问了类A的成员变量
void B::f(A& a) { a.m = 2; }
类
1.友元类
将类B声明为类A的友元类,类B所有的成员函数都可以访问类A的任何成员(包括私有成员)
class B;
class A {
private:
int m;
public:
friend class B;
};
class B {
public:
void f() {
A a;
a.m = 2;
}
};
(1) 顺序为:类B的声明,类A的定义,类B的定义
(2) 成员函数f()的函数体必须在类A
单向性:如果类A是类B的友元类,不意味着类B也是类A的友元类
非传递性:如果类A是类B的友元类,类B是类C的友元类,不意味着类A也是类C的友元类
类的对象成员
一个类对象是另一个类的成员
class B {
public:
int n;
B(int x) { n = x; }
};
class A {
public:
B b;//类B的对象是类A的数据成员
int m;
A(int x, int y) :b(x) { m = y; }//类A的构造函数中,对b进行初始化(会调用B的构造函数)
};
类成员构造顺序
先按照对象成员的声明顺序执行相应的构造函数,再调用自己的构造函数,析构函数顺序相反
class B {
public:
int n;
B(int x) { n = x; }
};
class A {
public:
int m;
B b1;
B b2;
A(int x1, int x2, int y) :b2(x2), b1(x1) {
m = y;
}
};
构造顺序:b1, b2, A自己;析构顺序:A自己,b2, b1
两个类相互包含
class A;//最前面需要放后定义的类的声明
class B {
public:
A* a;//先定义的类只能包含后定义类的指针或者引用
};
class A {
public:
B b;//后定义的类可以包含先定义类的对象
};
类不能包含自己的类对象,否则会陷入无限循环,类可以包含自己类的指针或者引用
八、类的运算符重载
运算符重载函数作为类成员函数A operator+(A &r){...}
1.重载‘+’运算符:
‘+’是双目运算符,有两个操作数,一个是调用重载函数的类对象(左操作数),另一个是参数r(右操作数)
2.重载‘++’运算符:
前缀++:返回++之后的值,因此通常返回类对象本身返回类型为引用
class A {
public:
int n;
A& operator ++ () {
n++;
return *this;
}
};
后缀++:需要返回++之前的值,因此函数通常返回对象的一个copy(返回类型非引用)(C++语言规定,在后缀++的重载函数的原型参数表中增加一个int 型的无名参数)
class A {
public:
int n;
A operator ++ (int) {
A s = *this;
n++;
return s;
}
};
void main() {
A a, b;
a.n = 0;
b.n = 1;
cout << (a++).n;
cout << (++b).n;
cout << a.n << b.n;
}
输出结果:0 2 1 2
3.重载‘=’运算符
如果返回值不是引用,(a=b)=c 会怎样?
a=b返回的不是a本身,而是一个临时变量,那么(a=b)=c相当于c的值最后没有赋值给a
如果形参不是const会有什么影响?
如果形参是非const类型的引用,实参必须为左值(即不能是表达式),例如,a = b+c 编译会出错
如果形参是const类型的引用,实参可以是非左值,例如,a = b + c 正确
4.运算符重载函数作为类的友元函数
class A {
private:
int n;
public:
friend A operator+(A&, A&);
};
A operator+(A& r1, A& r2) {
A c;
c.n = r1.n + r2.n;
return c;
}
本例中重载函数不是类成员函数,‘+’是双目运算符,有两个操作数,因此重载函数有两个参数(左/右操作数)
普通函数无法访问类的私有成员,因此,需要将重载函数声明为类的友元
只能选用友元函数的情形
1.若运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能选用友元函数
class A {
int i;
public:
A(int x) { i = x; }
A operator+(const A& a) const {
return A(i + a.i);
}
friend A operator-(const A&, const A&);
};
A operator-(const A& a1, const A& a2) {
return A(a1.i - a2.i);
}
int main() {
A a(3), b(4);
a + b;// ok, a.operator+(b)
a + 1;// ok, a.operator+(1),1隐式转换为A的对象
1 + a;// error, operator+的调用者必须为A的对象
a - b;// ok, operator-(a, b)
a - 1;// ok, operator-(a, 1),1隐式转换为A的对象
1 - a;// ok, operator-(1, a),1隐式转换为A的对象
return 0;
}
2.如果左边的操作数必须是一个不同类的对象,该运算符函数只能作为一个友元函数来实现:对类A重载<<运算符
class A {
int i;
public:
A(int x) { i = x; }
friend ostream& operator<<(ostream& os,
const A& a);
};
ostream& operator<<(ostream& os, const A& a) {
return os << a.i;
}
//<<是双目运算符,左边的操作数是ostream类的对象,右边是要输出的类对象
//如果把operator<<重载函数作为类A的成员函数,那么左操作数将是A的对象,无法实现cout在<<左侧