C++类&对象
文章目录
一、类和对象定义
1.对象
-
定义
通过类,可以定义变量。类定义出来的变量,也称为类的实例
-
对象的内存分配
和结构变量一样,对象所占用的内存空间的大小,等于所有成员变量的大小之和。
每个对象各有自己的存储空间。一个对象的某个成员变量被改变了,不会影响到另一个对象。 -
对象间的运算
和结构变量一样,对象之间可以用 “=”进行赋值,但是不能用 “==”,“!=”,“>”,“<”“>=”“<=”进行比较,除非这些运算符经过了“重载”。
2.类
-
定义:从客观事物抽象出类
类是 C++ 的核心特性,通常被称为用户定义的类型。
类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。 -
示例:矩形类
矩形的属性就是长和宽。因此需要两个变量,分别代表长和宽。
矩形可以有设置长和宽,算面积,和算周长这三种行为。
将长、宽变量和设置长,宽,求面积,以及求周长的三个函数“封装”在一起,就能形成一个“矩形类”。
class CRectangle
{
public:
int w, h;
int Area() {
return w * h;
}
int Perimeter(){
return 2 * ( w + h);
}
void Init( int w_,int h_ ) {
w = w_; h = h_;
}
}; //必须有分号
二、成员变量&成员函数
1.使用类的成员变量和成员函数
- 用法1:对象名.成员名
CRectangle r1,r2;
r1.w = 5;
r2.Init(5,4);
- 用法2. 指针->成员名
CRectangle r1,r2;
CRectangle * p1 = & r1;
CRectangle * p2 = & r2;
p1->w = 5;
p2->Init(5,4); //Init作用在p2指向的对象上
- 用法3:引用名.成员名
CRectangle r2;
CRectangle & rr = r2;
rr.w = 5;
rr.Init(5,4); //rr的值变了,r2的值也变
2.类成员的可访问范围
-
访问修饰符
类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。关键字 public、private、protected 称为访问修饰符。
-
公有(public)成员
公有成员在程序中类的外部是可访问的。可以不使用任何成员函数来设置和获取公有变量的值
-
私有(private)成员(无关键字默认)
私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。
默认情况下,类的所有成员都是私有的。 -
protected(受保护)成员
受保护成员变量或函数与私有成员十分相似,但有一点不同,protected(受保护)成员在派生类(即子类)中是可访问的
-
-
成员函数
-
成员函数内部
- 当前对象的全部属性、函数
- 同类其它对象的全部属性、函数
-
成员函数外部
- 只能够访问该类对象的公有成员
-
-
示例
class CEmployee
{
private:
char szName[30]; //名字
public:
int salary; //工资
void setName(char *name);
void getName(char *name);
void averageSalary(CEmployee e1, CEmployee e2);
};
void CEmployee::setName(char *name)
{
strcpy(szName, name); //ok
}
void CEmployee::getName(char *name)
{
strcpy(name, szName); //ok
}
void CEmployee::averageSalary(CEmployee e1,
CEmployee e2)
{
cout << e1.szName; //ok,访问同类其他对象私有成员
salary = (e1.salary + e2.salary) / 2;
}
int main()
{
CEmployee e;
strcpy(e.szName, "Tom1234567889"); //编译错,不能访
问私有成员
e.setName("Tom"); // ok
e.salary = 5000; //ok
return 0;
}
int main()
{
CEmployee e;
strcpy(e.szName, "Tom1234567889"); //编译错,不能访
问私有成员
e.setName("Tom"); // ok
e.salary = 5000; //ok
return 0;
}
-
隐藏
设置私有成员的机制,叫“隐藏”
“隐藏”的目的是强制对成员变量的访问一定要通过成员函数进行,那么以后成员变量的类型等属性修改后,只需要更改成员函数即可。否则,所有直接访问成员变量的语句都需要修改。
3.成员函数可以重载及参数缺省
- 成员函数重载
#include <iostream>
using namespace std;
class Location
{
private:
int x, y;
public:
void init(int x = 0, int y = 0);
void valueX(int val) { x = val; }
int valueX() { return x; }
};
- 成员函数缺省
class Location
{
private:
int x, y;
public:
void init(int x = 0, int y = 0);
void valueX(int val = 0) { x = val; }
int valueX() { return x; }
};
Location A;
A.valueX(); //错误,编译器无法判断调用哪个valueX
三、构造函数
1.构造函数概念
- 成员函数的一种
- 一个类可以有多个构造函数
2.构造函数定义
- 名字与类名相同,可以有参数,不能有返回值(void也不行)
3.构造函数生成
- 如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数,默认构造函数无参数,不做任何操作
- 如果定义了构造函数,则编译器不生成默认的无参数的构造函数
- 对象生成时构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数
4.构造函数作用
- 作用是对对象进行初始化,如给成员变量赋初值
- 构造函数执行必要的初始化工作,有了构造函数,就不必专门再写初始化函数,也不用担心忘记调用初始化函数。
- 有时对象没被初始化就使用,会导致程序出错。
5.构造函数示例
- 编译器自动生成
class Complex
{
private:
double real, imag;
public:
void Set(double r, double i);
}; //编译器自动生成默认构造函数
Complex c1; //默认构造函数被调用
Complex *pc = new Complex; //默认构造函数被调用
- 可以有多个构造函数,参数个数或类型不同
class Complex
{
private:
double real, imag;
public:
void Set(double r, double i);
Complex(double r, double i);
Complex(double r);
Complex(Complex c1, Complex c2);
};
Complex::Complex(double r, double i)
{
real = r;
imag = i;
}
Complex::Complex(double r)
{
real = r;
imag = 0;
}
Complex::Complex(Complex c1, Complex c2);
{
real = c1.real + c2.real;
imag = c1.imag + c2.imag;
}
Complex c1(3), c2(1, 0), c3(c1, c2);
// c1 = {3, 0}, c2 = {1, 0}, c3 = {4, 0};
- 构造函数最好是public的,private构造函数不能直接用来初始化对象
class CSample
{
private:
CSample()
{
}
};
int main()
{
CSample Obj; //err. 唯一构造函数是private
return 0;
}
四、复制构造函数
1.复制构造函数基本概念
- 只有一个参数,即对同类对象的引用
- 形如 X::X( X& )或X::X(const X &)
- 如果没有定义复制构造函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能
- 如果定义的自己的复制构造函数,则默认的复制构造函数不存在。
- 不允许有形如 X::X( X )的构造函数
2.复制构造函数起作用的三种情况
- 1)当用一个对象去初始化同类的另一个对象时。
Complex c2(c1);
Complex c2 = c1; //初始化语句,非赋值语句
- 2)如果某函数有一个参数是类 A 的对象,那么该函数被调用时,类A的复制构造函数将被调用。
class A
{
public:
A(){};
A(A &a)
{
cout << "Copy constructor called" << endl;
}
};
void Func(A a1) {}
int main()
{
A a2;
Func(a2);
return 0;
}
//程序输出结果为: Copy constructor called
-
- 如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用:
class A
{
public:
int v;
A(int n) { v = n; };
A(const A &a)
{
v = a.v;
cout << "Copy constructor called" << endl;
}
};
A Func()
{
A b(4);
return b;
}
int main()
{
cout << Func().v << endl;
return 0;
}
//输出结果:
//Copy constructor called
//4
3.注意:对象间赋值并不导致复制构造函数被调用
五、类型转换构造函数
1.类型转换构造函数基本概念
- 定义转换构造函数的目的是实现类型的自动转换。
- 只有一个参数,而且不是复制构造函数的构造函数,一般就可以看作是转换构造函数。
- 当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)。
2.类型转换构造函数示例
class Complex
{
public:
double real, imag;
Complex(int i)
{ //类型转换构造函数
cout << "IntConstructor called" << endl;
real = i;
imag = 0;
}
Complex(double r, double i)
{
real = r;
imag = i;
}
};
int main()
{
Complex c1(7, 8);
Complex c2 = 12;
c1 = 9; // 9被自动转换成一个临时Complex对象
cout << c1.real << "," << c1.imag << endl;
return 0;
}
六、析构函数
1.析构函数基本概念
- 名字与类名相同,在前面加‘~’, 没有参数和返回值,一个类最多只能有一个析构函数。
- 析构函数对象消亡时即自动被调用。可以定义析构函数来在对象消亡前做善后工作,比如释放分配的空间等。
- 如果定义类时没写析构函数,则编译器生成缺省析构函数。缺省析构函数什么也不做。
- 如果定义了析构函数,则编译器不生成缺省析构函数。
2.析构函数示例
class String
{
private:
char *p;
public:
String()
{
p = new char[10];
}
~String();
};
String ::~String()
{
delete[] p;
}
七、this指针
1.this指针的作用
- 非静态成员函数中可以直接使用this来代表指向该函数 作用的对象的指针。
class Complex
{
public:
double real, imag;
void Print() { cout << real << "," << imag; }
Complex(double r, double i) : real(r), imag(i)
{
}
Complex AddOne()
{
this->real++; //等价于 real ++;
this->Print(); //等价于 Print
return *this;
}
};
2.this指针和静态成员函数
-
静态成员函数中不能使用 this 指针
因为静态成员函数并不具体作用与某个对象
八、静态成员
1.静态成员基本概念
- 在定义前面加了static关键字的成员
class CRectangle
{
private:
int w, h;
static int nTotalArea; //静态成员变量
static int nTotalNumber;
public:
CRectangle(int w_, int h_);
~CRectangle();
static void PrintTotal(); //静态成员函数
}
-
静态成员&普通成员
- 普通成员变量每个对象有各自的一份,而静态成员变 量一共就一份,为所有对象共享。
//sizeof 运算符不会计算静态成员变量。
class CMyclass
{
int n;
static int s;
};
//则 sizeof( CMyclass ) 等于 4
- 普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象。
2.静态成员的访问
静态成员不需要通过对象就能访问
-
- 类名::成员名
CRectangle::PrintTotal();
-
- 对象名.成员名
CRectangle r; r.PrintTotal();
-
- 指针->成员名
CRectangle * p = &r; p->PrintTotal();
-
- 引用.成员名
CRectangle & ref = r; int n = ref.nTotalNumber;
3.静态成员注意事项
- 在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。
九、成员对象和封闭类
1.封闭类定义
有成员对象的类叫 封闭(enclosing)类
class CTyre //轮胎类
{
private:
int radius; //半径
int width; //宽度
public:
CTyre(int r, int w) : radius(r), width(w) {}
};
class CEngine //引擎类
{
};
class CCar
{ //汽车类
private:
int price; //价格
CTyre tyre;
CEngine engine;
public:
CCar(int p, int tr, int tw);
};
CCar::CCar(int p, int tr, int w) : price(p), tyre(tr, w){};
int main()
{
CCar car(20000, 17, 225);
return 0;
}
2.封闭类构造函数和析构函数的执行顺序
- 封闭类对象生成时,先执行所有对象成员的构造函数,然后才执行封闭类的构造函数。
- 对象成员的构造函数调用次序和对象成员在类中的说明次序一致,与它们在成员初始化列表中出现的次序无关。
- 当封闭类的对象消亡时,先执行封闭类的析构函数,然后再执行成员对象的析构函数。次序和构造函数的调用次序相反。
3.封闭类实例
class CTyre
{
public:
CTyre() { cout << "CTyre contructor" << endl; }
~CTyre() { cout << "CTyre destructor" << endl; }
};
class CEngine
{
public:
CEngine() { cout << "CEngine contructor" << endl; }
~CEngine() { cout << "CEngine destructor" << endl; }
};
class CCar
{
private:
CEngine engine;
CTyre tyre;
public:
CCar() { cout << “CCar contructor” << endl; }
~CCar() { cout << "CCar destructor" << endl; }
};
4.封闭类的复制构造函数
封闭类的对象,如果是用默认复制构造函数初始化的,那么它里面包含的成员对象, 也会用复制构造函数初始化。
十、友元
1.友元函数
- 一个类的友元函数可以访问该类的私有成员
class CCar; //提前声明 CCar类,以便后面的CDriver类使用
class CDriver
{
public:
void ModifyCar(CCar *pCar); //改装汽车
};
class CCar
{
private:
int price;
friend int MostExpensiveCar(CCar cars[], int total); //声明友元
friend void CDriver::ModifyCar(CCar *pCar); //声明友元
};
友元(friends, P199) void CDriver::ModifyCar(CCar *pCar)
{
pCar->price += 1000; //汽车改装后价值增加
}
int MostExpensiveCar(CCar cars[], int total)
//求最贵汽车的价格
{
int tmpMax = -1;
for (int i = 0; i < total; ++i)
if (cars[i].price > tmpMax)
tmpMax = cars[i].price;
return tmpMax;
}
int main()
{
return 0;
}
2.友元类
- 如果A是B的友元类,那么A的成员函数可以访问B的私有成员
class CCar
{
private:
int price;
friend class CDriver; //声明CDriver为友元类
};
class CDriver
{
public:
CCar myCar;
void ModifyCar()
{ //改装汽车
myCar.price += 1000; //因CDriver是CCar的友元类,
//故此处可以访问其私有成员
}
};
int main() { return 0; }
3.友元类注意事项
友元类之间的关系不能传递,不能继承
十一、常量成员函数
如果一个成员函数中没有调用非常量成员函数 ,也没有修改成员变量的值,那么,最好将其 写成常量成员函数。
1.常量成员函数基本概念
- 在定义常量成员函数和声明常量成员函数时都应该使用const 关键字
class Sample
{
private:
int value;
public:
void PrintValue() const;
};
void Sample::PrintValue() const
{ //此处不使用const会
//导致编译出错
cout << value;
}
void Print(const Sample &o)
{
o.PrintValue(); //若 PrintValue非const则编译错
}
- 常量成员函数内部不能改变属性的值,也不能调用非常量成员函数
class Sample
{
private:
int value;
public:
void func(){};
Sample() {}
void SetValue() const
{
value = 0; // wrong
func(); //wrong
}
};
const Sample Obj;
Obj.SetValue(); //常量对象上可以使用常量成员函数
2.常量成员函数的重载
- 两个函数,名字和参数表都一样,但是一个是const,一个不是,算重载。
int main()
{
const CTest objTest1;
CTest objTest2;
cout << objTest1.GetValue() << "," << objTest2.GetValue();
return 0;
}
//= > 1, 2
3.mutable成员变量
- 可以在const成员函数中修改的成员变量
class CTest
{
public:
bool GetData() const
{
m_n1++;
return m_b2;
}
private:
mutable int m_n1;
bool m_b2;
};