面向对象的程序设计
面向对象(Object Oriented, OO)是当代计算软件开发方法;
对象:现实世界中的各种具体的或抽象的“事物”。
一 、类
类是具有相同属性和行为的一组对象的集合,它为属于该类的全部对象提供了统一的抽象描述,其内部包括属性和行为两个主要部分
1、语法格式:
class 类名称
{
public://访问权限修饰符
公有成员(外部可以调用)
protected://访问权限修饰符
保护成员(外部不可调用、派生类可以调用)
private://访问权限修饰符
私有成员(外部不可调用、派生类不可调用)
};
2、类对象
类的对象是该类的某一特定实体,即类类型的变量。
声明形式: 类名 对象名;
3、类成员
成员变量
class 类名
{
访问权限:
类型 变量名; //声明的时候不占用内存
};
注意:在定义成员变量时,不对成员变量赋初始值。
成员函数
可以写在类体重也可以写在类体外;
示例:
#pragma once #include <iostream> class Color { public: void setName(const char* n) { strcpy_s(name, n); } private: char name[32]; }
1 Color.h 2 #pragma once 3 #include <iostream> 4 5 class Color 6 { 7 public: 8 void setName(const char* n); 9 private: 10 char name[32]; 11 }; 12 13 Color.cpp 14 #include "stdafx.h" 15 #include "Color.h" 16 17 18 void Color::setName(const char* n) 19 { 20 strcpy_s(name, n); 21 }
4、类成员的访问
(1)类中成员可以直接访问
直接使用成员名。
(2)类外访问
对象使用“对象名.成员名”的方式访问public属性的成员。
指针使用“指针变量->成员名”的方式访问public属性的成员。
#include "stdafx.h" #include <iostream> using namespace std; class Car//定义car类 { public: void start()//成员函数 { cout << color << "的" << name << "启动了" << endl; } void stop()//成员函数 { cout << color << "的" << name << "停下了" << endl; } void set(const char * n, const char * c)//成员函数 { strcpy_s(name, n); strcpy_s(color, c); } private: char name[32];//成员变量 char color[32];//成员变量 }; int main() { Car a;//定义一个car类的对象a a.set("宝马", "红色"); a.start(); a.stop(); Car b; b.set("奔驰", "蓝色"); Car *p = &b; p->start(); p->stop(); return 0; }
二、构造函数
概念:构造函数是实现数据成员初始化的特殊成员函数
特点:(1)与类同名,没有返回值;
(2)创建对象时,构造函数被自动调用。每创建一个对象都必须调用一次构造函数,每调用一次构造函数必定创建一个对象。
分类 : (1)无参构造函数:没有参数;
(2)普通构造函数:普通参数;
(3) 拷贝构造函数:参数为对象的引用。
语法格式:
(1)类中定义格式
类名(形参列表)
{…} //函数体,对数据成员赋值 类中声明,
(2)类外定义
类中声明 类名(形参列表);
类外定义 类名::类名(形参列表)
{…} //函数体
一、缺省的构造函数
1、缺省构造函数的种类
(1)系统自动产生的构造函数 类名() {}
Α、 用户未定义构造函数时,系统会自动产生构造函数,用户一旦定义,系统就不会再产生构造函数。
(2) 用户定义的无参构造函数
class String { public: String() //无参构造函数 { memset(str, 0, sizeof(str)); cout << "用户定义无参构造函数" << endl; }
(3)用户定义的所有参数都有缺省值的构造函数。
1 class String 2 { 3 public: 4 String(char *s = nullptr) //用户定义的所有参数都有缺省值的构造函数 5 { 6 cout << "所有参数都有缺省值" << endl; 7 if (s == nullptr) 8 { 9 memset(str, 0, sizeof(str)); 10 } 11 else 12 { 13 strcpy_s(str, s); 14 } 15 }
2、缺省的构造函数的调用
类名 类对象
定义类对象时 有两件事发生:Α 、创建了类对象 。 Β、调用构造函数。
二、有参的构造函数
1、有参构造函数的定义
1 class String 2 { 3 public: 4 String(char * p) //有参构造函数 5 { 6 memset(str, 0, sizeof(str)); 7 cout << "用户定义有参构造函数" << endl; 8 }
2、有参构造函数的调用
int main() { String s("Hello world!"); s.output(); return 0; }
三、拷贝构造函数
概念:拷贝构造函数是一种特殊的构造函数,其形参为本类的对象的引用
作用:从一个已有对象,来初始化新创建的对象
语法格式:
class 类名
{
public: 类名() {…} //无参构造函数
类名(形参列表) {…} //有参构造函数
类名(类名& 对象名) {…} //拷贝构造函数 };
// 调用拷贝构造函数的场景(假设有Car c1定义):
Car c2 = c1;
Car c3(c1);
class String { public: String(const char* s) { //memset(str, 0, sizeof(str)); strcpy_s(str, s); } //拷贝构造函数 String(const String& other) //一般都这么写other { strcpy_s(str, other.str); } void output() { cout << str << endl; } private: char str[128]; }; int main() { String a("123453333333333333333333333333333333333"); String b(a); cout << "a="; a.output(); cout << "b="; b.output(); return 0; }
四、浅拷贝和深拷贝
从已存在对象来创建新的对象,系统会调用拷贝构造函数,如果用户没有定义拷贝构造函数,则系统会自动生成默认的拷贝构造函数,进行值拷贝。如果用户定义了拷贝构造函数,系统会调用用户顶替拷贝构造函数,进行拷贝。
(1)默认拷贝构造函数可以完成对象的数据成员简单的复制,实际上默认的拷贝构造函数只是将指针复制。
(2)在对含有指针成员的对象进行拷贝时,必须要自己定义拷贝构造函数,使拷贝后的对象指针成员有自己的内存空间,即进行深拷贝,这样就避免了内存泄漏发生。
浅拷贝:类成员变量中有指针,文件、句柄等情况下,浅拷贝就有可能让类无法正常工作。
原因:以指针为例,拷贝只是复制了指针变量的地址,而其内存数据未拷贝,一旦s1释放内存,等到s2释放内存时,已找不到指针所指向的内存,此时为野指针,带来的后果具有不可预测性,程序执行到此会崩溃,
深拷贝:让新产生的对象,其成员变量对资源的引用操持独立,相互之间不受影响,仅保持“值”相同。
深拷贝示例
#include "stdafx.h" #include <iostream> using namespace std; class Student { private: int num; char *name; public: Student(); ~Student(); Student(const Student &s);//拷贝构造函数,const防止对象被改变 }; Student::Student() { name = new char(20); cout << "Student" << endl; } Student::~Student() { cout << "~Student " << (int)name << endl; delete name; name = NULL; } Student::Student(const Student &s) { name = new char(20); memcpy(name, s.name, strlen(s.name)); cout << "copy Student" << endl; } int main() { { Student s1; Student s2(s1);// 复制对象 } return 0; }
总结: 浅拷贝会把指针变量的地址复制; 深拷贝会重新开辟内存空间。
三、析构函数
1、概念:用于撤销对象的成员函数造成的垃圾,比如开辟的空间(对象释放前系统自动调用)。、
2、语法格式:
(1)类中定义
~类名()
{…} //函数体
(2) 类中说明,类外定义
Α 类中声明
~类名(); //类中声明
Β 类外定义
类名::~类名()
{…} //函数体
3、注意事项:
(1)析构函数的名称由运算符“~”与类名组成;
(2)析构函数无参数,无返回值;
(3)析构函数不可以重载,即一个类只有一个析构函数;
(4)如果用户没有定义,系统会自动生成默认析构函数: 类名::~类名() {}
(5)当类中用new运算符分配了动态空间时,必须定义析构函数,并在函数体中用delete运算符释放动态存储空间。
(6)析构函数调用与构造函数相反,先构造的后析构,后构造的先析构;
4、示例
#include "stdafx.h" #include <iostream> #include <string.h> using namespace std; class String { public: String(const char *str) { int len = strlen(str) + 1; p = new char[len]; strcpy_s(p, len, str); cout << "创建对象" << endl; } void output() { cout << p << endl; cout << "打印对象" << endl; } ~String() { if (p != nullptr) { delete [ ] p; p = nullptr; cout << "对象析构" << endl; } } private: char *p; }; int main() { String s("hello world"); s.output(); return 0;
四、成员变量初始化
1、类成员变量初始有两种方式
方式一: 在构造函数体中对成员变量进行赋值
语法: 类名(形参列表)
{
成员变量1 = 表达式;
成员变量2 = 表达式;
…
成员变量n = 表达式;
}
方式二:使用初始化列表
语法: 类名(形参列表) : 对象名1(实参列表) , 对象名1(实参列表) , … ,对象名n(实参列表)
{
… //函数体
}
2、成员变量初始化的顺序
(1)成员变量的初始化顺序和成员变量声明顺序相关,先声明,先初始化
(2)先调用对象成员所属类的构造函数,再执行自身类的函数体
五、成员变量和成员变量函数
1、静态成员
分类:静态成员变量和静态成员函数
静态成员变量
1、格式语法
class 类名 {
访问权限:
static 类型 变量名;
};
2、说明:
(1)静态成员变量不属于某个对象,而是属于类,它由某个类的所有对象共有。
(2)静态成员变量的定义 静态成员变量定义后,必须对它进行初始化,并且一定要在类外进行。
(3)静态成员变量只能够初始化一次
(4)初始化的形式如下: 类型 类名::静态成员变量 = 表达式; 初始化时不加关键字static
3、示例:
#include "stdafx.h" #include <iostream> using namespace std; class Point { int x; int y; public: static int no;//声明静态成员变量 Point(int a, int b) :x(a), y(b) { ++no; } }; int Point::no = 0;//必须在类体外初始化静态成员变量 void main() { Point p(1,10); Point p1(2,4); cout << Point::no << endl;//调用静态成员变量 }
静态成员函数
语法格式:
class 类名 {
访问权限:
static 返回值 函数名(形参列表);
}
说明:
(1)静态成员函数也从属于类,由同一个类的所有对象共同拥有。
(2)静态成员函数只能直接访问该类的静态成员变量、静态成员函数以及类以外的数据和函数,而访问非静态成员必须通过参数传递方式得到类的对象,然后通过对象名来访问。
(3)在类内可以直接通过函数名称访问。
(4)在类外可以通过 类名::静态成员函数(实参)、对象.静态成员函数(实参)、指针->静态成员函数(实参) 来访问。
class Point { int x, y; static int no; //声明静态成员变量 public: Point(int a, int b) : x(a), y(b) { ++no; } static void print() { //静态成员函数 std::cout << “no=” << no << std::endl; } }; int Point::no = 0; void main(){ Point pt(1, 10); Point* p1 = new Point(3, 3); pt.print(); //对象名.静态成员函数名() p1->print(); //指针->静态成员函数名() Point::print(); //类名::静态成员函数名() }
六、函数的重载
概念:函数重载是指在同一作用域内,可以有一组相同函数名,不同参数列表的函数,这组函数被称为重载函数。
作用:达到行为标识符统一
判断依据:函数名称、形参列表(参数类型或个数)和返回值无关
示例
#include "stdafx.h" #include <iostream> using namespace std; int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } const char* add(char *a,char *b) { strcat_s(a,100,b); return a; } int main() { char s[100] = {"hello "}; cout << add(10, 10) << endl; cout << add(10.9, 10.5) << endl; cout << add(s, "world") << endl; return 0;
常成员函数:(1)在一个类中使用const关键字说明的成员函数,称为常成员函数
(2)常成员函数不能修改成员变量的值,也不能调用该类中其它没有用const修饰的成员函数。
#include "stdafx.h" #include <iostream> using namespace std; class S { int a; public: S(int x) :a(x) { } void print() { cout << a << endl; } void print()const; }; void S::print()const { cout <<"const"<< a << endl; } int main() { S a(10); a.print();//调用非常成员函数 const S& a1 = a; a1.print();//调用常成员函数 return 0; }
运算符重载
概念:运算符重载就是赋予已有运算符多重含义
语法格式:
class 类名
{
public:
类型 operator 运算符(形参列表)
{ 函数体 }
};
原则:
(1)被重载的运算符不改变原来的操作数个数、优先级和结合性。
(2)不能创新发明。
(3)不能改变运算符对预定义类型(基本类型)的操作方式。
(4)成员函数实现运算符重载时,运算符的左操作数为当前对象。
(5)如果重载的运算符函数是双目的运算符,则参数表中有一个参数,若为单目运算符,则参数表中有零个参数(没有参数),特殊情况除外,比如++、--运算符。
不可重载的运算符:
实现方式:成员函数 和 友员函数