面向对象是相对于面向过程来讲的,面向对象方法,把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。
一切事物皆对象。
类
类是具有相同属性和行为的一组对象的集合,它为属于该类的全部对象提供了统一的抽象描述,其内部包括属性和行为两个主要部分。
类有两个非常大的优势:
- 利用类可以实现数据的封装、隐藏、继承和派生
- 利用类易于编写大型复杂程序,其模块化程序比C中采用函数更高
类声明语法:
class 类名称
{
public:
公有成员(外部可以调用)
protected:
保护成员(外部不可调用、派生类可以调用)
private:
私有成员(外部不可调用、派生类不可调用),类默认为私有
};
在C++中,类的默认访问权限为私有,这体现了面向对象中封装的特点,这也是类与结构体最大的不同。
以汽车的类定义与使用为例:
class Car
{
public: //访问权限为公有
void set(const char * n, const char * c) //成员函数
{
strcpy_s(name, n);
strcpy_s(color, c);
}
void start()
{
cout << color << name << "启动" << endl;
}
void stop()
{
cout << color << name << "停住了" << endl;
}
private: //访问权限为私有
char name[32]; //成员变量
char color[32];
};
int main()
{
Car c; //定义对象(实例化对象)
c.set("奔驰", "黑色");
c.start();
c.stop();
return 0;
}
C++中的类
类的常用书写规范
类名:大驼峰
类的头文件:类名.h
类的源文件:类名.cpp
函数名:小驼峰
在书写类文件时,头文件放声明,源文件放实现。在源文件里实现类的成员函数时需在函数名前面加上类名,否则源文件的函数会成为全局函数,不能对应实现成员函数功能。
以一个Color类为例:
//头文件内容
#include <iostream>
using namespace std;
class Color
{
public:
void setName(const char * n);
private:
char name[32];
};
//源文件内容
#include "Color.h"
void Color::setName(const char * n) //函数名前加类名使源文件中的函数成为类的成员函数
{
strcpy_s(name, n);
cout << "Color is " << name << endl;
}
函数的参数默认值
书写规范:
- 默认参数放在函数声明处
- 从右缺省(c/c++传参从左到右对应,左边有默认值右边没有默认值时右边参数会没有值)
//头文件
void test(int a, int b = 0, int c = 0); //从右缺省
//源文件
void test(int a, int b, int c)
{
cout << b << c << endl; //test()输出00
}
内联函数
将函数的调用展开到调用的位置,放弃函数调用以提高程序执行效率。这样做的好处是省去了函数调用的过程,加快程序运行速度,但由于代码调用到内联函数,就需要在调用处直接插入一段该函数的代码,所以程序的体积将增大。内联函数策略是以空间换取时间。在现在硬件水平比较高的时代,内联函数使用不多。
使用语法:在函数前加inline关键字
class Light
{
public:
inline void test();
};
void Light ::test();
{
cout << “open” << endl;
}
内联函数的使用由系统自己判断,在代码比较短时,使用内联函数所增大空间性价比不如调用函数执行时不会使用内联而选择直接调用。
构造函数
作用:初始化成员变量,避免不正确的成员变量访问。
特征:
- 与类同名
- 无返回值(直接不写返回值类型)
- 自动调用(每生成一个对象就调用一次)
class Color
{
public:
void setName(const char * n);
private:
char name[32];
};
Color::Color()
{
strcpy_s(name, "NULL");
}
根据参数分类
- 无参构造:没有给成员变量赋初值时自动执行
- 有参构造:给成员变量赋初值调用有参构造
- 拷贝构造:从一个已有对象初始化新建对象
class A
{
public:
A(){ //无参构造
strcpy_s(str, "NULL");
cout << "无参" << endl;
}
A(const char * p) { //有参构造
strcpy_s(str, p);
cout << "有参" << endl;
}
A(const A & other){
strcpy_s(str, other.str);
cout << "拷贝" << endl;
}
void output() {
cout << str << endl;
}
private:
char str[64];
};
int main()
{
A a1;
a1.output();
A a2("ming");
a2.output();
A a3(a2); // 或A a3 = a2;
a3.output();
return 0;
}
根据调用分类
- 默认(缺省)构造
- 无参
- 所有参数都有默认值
- 系统自动生成
- 有参构造
- 拷贝构造
每个类中只能有一个构造函数,否则会报错。
A(int a = 5) { //函数都有默认值的构造函数
cout << a << endl;
}
A() //无参默认构造,两个默认构造放一个类中无法创建对象
{
cout << a << endl;
}
析构函数
如果在类中申请了堆区空间,为了程序不出现内存问题可以正常运行,那么也要在类中完成指针的释放。析构函数的作用就是为了释放成员变量。
析构函数特点:
- 命名为**~类名**
- 无参,无返回值
- 自动调用。调用时间为对象离开作用域
- 有且只有一个
class String
{
private:
char * p;
public:
String(const char * str) {
int len = strlen(str) + 1;
p = new char[len];
strcpy_s(p, len, str);
}
~String() { // 在类中定义析构函数
delete[]p;
}
void output() {
cout << p << endl;
}
};
delete和free区别:
delete可以调用析构函数,free不能调用。所以free不能用于C++中对对象的操作。
拷贝构造函数
从已存在对象来创建新的对象,系统会调用拷贝构造函数,如果用户没有定义拷贝构造函数,则系统会自动生成默认的拷贝构造函数,进行值拷贝。值拷贝是将被拷贝的对象成员变量赋给拷贝对象的成员变量。根据是否需要开发者自行实现拷贝构造函数分为三类。
- 无需实现
不需要自己实现的拷贝构造函数的特点通常是没有指针变量,对象的拷贝只需要将值赋予新的对象成员变量。
如下例:
class Student
{
private:
char name[32]; int age;
public:
Student(const char* n, int a)
{
strcpy_s(name, n);
age = a;
}
};
void main()
{ Student s1(“小明”, 18);
Student s2(s1);
}
- 需要实现
在一些情况下需要自己实现拷贝构造函数,如拷贝构造函数涉及到动态申请存储空间。如果只是简单的将对象中成员变量指针的值赋给新的对象,那么新的对象进行堆区时会操作时会影响被拷贝的对象。所以需要开发者自己实现拷贝构造函数。
如下例假设不自己实现拷贝构造函数:
class String
{
private:
char* str;
public:
String(const char* s)
{
int len = strlen(s) + 1;
str = new char[len];
strcpy_s(str, len, s);
}
void output() {
std::cout<<str<<endl;
}
~String() {
delete [] str;
}
};
int main()
{
String s1("123");
{
String s2 = s1;
}
s1.output(); // 此处程序崩溃
return 0;
}
程序崩溃原因是默认的构造函数只是将s1指针的值赋给s2的指针,使得s1与s2共用一块堆区的存储空间。s2在结束自己的生命周期时调用析构函数释放了堆区空间,导致s1成为了野指针。
所以需要开发者自己实现拷贝构造函数:
class String
{
private:
char* str;
public:
String(const char* s)
{
int len = strlen(s) + 1;
str = new char[len];
strcpy_s(str, len, s);
}
void output() {
std::cout<<str<<endl;
}
~String() {
delete [] str;
}
String(String& other) { // 在堆区开辟新的存储空间进行拷贝
int len = strlen(other.str) + 1;
str = new char[len];
strcpy_s(str, len, other.str);
}
};
- 无法实现
在一些情况下,拷贝构造函数一定会影响被拷贝的对象,所以不能使用拷贝构造函数。
如下例:
class File
{
private:
FILE * fp;
public:
File::File(const char * str)
{
errno_t err = fopen_s(&fp, str, "w" );
}
void File::write(const char * str)
{
fprintf_s(fp, str);
cout << "write success" << endl;
}
File::~File()
{
fclose(fp);
}
};
int main()
{
File f1("1.txt"){
File f2 = f1;
}
f1.write("ABC"); // 此处程序崩溃
return 0;
}
程序崩溃原因是f2在结束生命周期时关闭了文件,f1不能进行写入。这种情况无法避免,所以开发者要删除拷贝构造函数。在类中添加File(const File &) = delete;
使不能进行对象的拷贝操作。
常成员函数
在一些情况下对成员函数要求不能对类的成员变量进行修改,此时就需要在成员函数后面加const修饰符使成员变为常成员函数。
class A
{
int a;
public:
A(int n) {
a = n;
}
void updata(){
a = 5;
}
void output() const{ //常成员函数
cout << a << endl;
}
};
成员变量初识化
成员变量一般有三种初始化方式:
- 在构造函数体中
- 在构造函数初始化列表中
- 在声明变量时
class A
{
private:
int a1;
int a2, a3;
int a4 = 4; //在声明变量时
public:
A() :a2(2), a3(3) //在构造函数初始化列表
{
a1 = 1; //在构造函数体
}
};
成员变量的初识化过程中,成员变量初始化顺序与声明顺序有关,先声明的先初始化。
this指针
this是C++的一个关键字,也是一个const指针,this指针用于指向当前对象。this指针常用于解决同名冲突。
class Student
{
public:
Student(const char * name, const int age) {
strcpy_s(this->name, name);
this->age = age;
}
void output() {
cout << this->name << endl;
cout << age << endl; //this指针是隐含的,没有同名冲突时可以不写
}
private:
char name[64];
int age;
};
静态成员
在类中有两种静态成员,分别是
- 静态成员变量
- 静态成员函数
静态成员变量
静态成员变量的目的是为实现在不同对象之间的数据共享。
数据存在与静态全局区,不占用对象的存储空间。每个类只有一个静态成员变量的拷贝,由该类的所有对象共同使用和维护。静态成员函属于类,不属于某个对象。
静态成员变量定义:
class 类名
{
访问权限:
static 类型 变量名;
}
在静态成员变量定义后,一定要对其进行初始化,并且初识化要在类外进行。
静态成员变量初始化:
类型 类名::静态成员变量名 = 表达式 //初始化时不需要加关键字static
静态成员的访问有两种方式:
对象名.静态成员变量;
类名::静态成员变量; //建议使用这一种,体现静态成员变量属于整个类不属于某个对象
含有静态成员变量的类举例:
class Point
{
int x, y;
public:
static int no; //声明静态成员变量
Point(int a, int b) : x(a), y(b)
{
++no; //类内使用静态成员变量
}
};
int Point::no = 0; //类外初始化静态成员变量
int main(){
Point p(1, 10);
Point p1(2, 4);
cout << Point::no << endl; //类外访问静态成员变量
return 0;
}
静态成员函数
静态成员函数属于类,由一个类的所有对象共有。静态成员函数只能直接访问该类的静态成员变量、静态成员函数、类以外的数据和函数。
静态成员函数定义:
class 类名
{
访问权限:
static 返回类型 函数名(形参列表);
}
静态成员函数使用:
类内:
函数名(实参列表)
类外:
类名::静态成员函数(实参列表)
对象.静态成员函数(实参列表)
指针->静态成员函数(实参列表)
友员
类是经过封装的黑盒,外部不能访问类的私有成员。有时在类的外部需要访问类的私有成员,可以将一些函数、类、类的成员函数定义为该类的友员,友员的作用就是使外部可以通过函数、类、类的成员函数直接访问类的私有成员。友员会破坏类的封装性,降低类的可靠性和可维护性,不能滥用友员。
友员的使用就是在函数或类前加friend关键字,是其可以访问私有成员。
友员声明:
class 类名
{
friend 数据类型 类名::友员成员函数名(形参列表);
}
友员主要包括友员函数、友员类、友员成员函数。其中较为常用的是友员函数。
友员函数是一个全局函数,友员函数的定义可以在类外也可以在类内:
class
{
private:
int a;
friend void output(A& o); //类外定义
public:
A(int x) : a(x)
{
}
friend void test(A& o) //类内定义
{
cout << "类外定义" << o.a << std::endl;
}
};
void output(A& o)
{ std::cout << "类内定义" <<o.a << std::endl;}
int main()
{
A t(100);
output(t); //两种方式都可以完成对私有成员变量a的访问
test(t);
return 0;
}