面向对象的三大特性
封装,继承,多态
封装性
将数据和方法封装在一起
类的访问限定符分为公有的:public,私有的:private,protected。若类中无访问限定符,则默认为私有的
类的作用域
对象通过 . 直接访问公有成员,指向对象的指针通过 -> 也可以直接访问对象的公有成员
#include <iostream>
using namespace std;
class Person
{
public:
void Display()
{
cout << _name << "-" << _sex << "-" << _age << endl;
}
public:
char* _name;
char* _sex;
int _age;
};
void Test()
{
Person p;
p._name = "lisa";
p._age = 15;
p._sex = "女";
p.Display();
Person* ptr = &p;
ptr->_name = "peter";
ptr->_age = 18;
ptr->_sex = "男";
ptr->Display();
}
int main()
{
Test();
system("pause");
return 0;
}
在类外定义成员,需使用 :: 作用域解析符指明成员属于哪个类域
class Person
{
public:
void Display();
public:
char* _name;
char* _sex;
int _age;
};
void Person::Display()
{
cout << _name << "-" << _sex << "-" << _age << endl;
}
类对象存储
每个对象的大小为类中所有成员变量的大小之和,内存对齐
成员函数属于公共函数,不算入类的大小,不同对象均调用同一个函数,节省空间
空类
没有成语变量的类
#include <iostream>
using namespace std;
class A
{
};
class B
{
void func()
{
}
};
int main()
{
cout << sizeof(A) << endl;//1
cout << sizeof(B) << endl;//1
system("pause");
return 0;
}
为什么空类的大小为1?
若对类实例化,给A和B分别定义一个成员对象,A a和B b,则为了区分成员对象分别开辟一个字节空间标识a和b
隐含的this指针
1.每个成员函数都有一个指针形参,即为this指针(构造函数无this指针)
2.编译器对成员函数进行处理,在对象调用成员函数时,对象地址作实参传递给成员函数的this指针
3.this指针是成员函数隐含指针形参,是编译器自己处理的,即我们不能在成员函数的形参中添加this指针,也不能在调用时显
示传递对象的地址给this指针
为什么没有给成员函数传参,却可以打印出不同的数据?
隐式传参,this指针
类的默认成员函数
数据:private 函数:public
构造函数
成员变量为私有的,要对他们进行初始化,必须用一个公有函数来进行,同时这个函数有且仅在定义对象时自动执行一次
特征:
1.函数名与类名相同
2.无返回值
3.对象实例化时系统自动调用对应的构造函数
4.构造函数可以重载
5.构造函数可以在类内定义,也可以在类外定义
6.若类定义中没有给出构造函数,则C++编译器自动产生一个缺省的(无参)构造函数。若我们自己定义了一个构造函数,系统
不会自动生成缺省的构造函数
7.无参的构造函数和全缺省的构造函数都认为是缺省构造函数,不可同时存在于一个类中
#include <iostream>
using namespace std;
class Date
{
public:
//无参的构造函数
Date()
{
}
//带参的构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void show()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.show();
Date d2(1990, 1, 1);
d2.show();//1990-1-1
system("pause");
return 0;
}
#include <iostream>
using namespace std;
class Date
{
public:
//全缺省参数的构造函数
Date(int year = 2018, int month = 3, int day = 19)
{
_year = year;
_month = month;
_day = day;
}
void show()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
d.show();//2018-3-19
system("pause");
return 0;
}
初始化列表
以一个冒号开始,接着一个逗号分隔数据列表,每个数据成员都放在一个括号中进行初始化
无论是否采用初始化列表进行初始化,编译器一定会走一遍初始化列表这个过程
哪些成员变量必须放在初始化列表里?
1.常量成员变量(创建时必须初始化) const int N;
2.引用类型成员变量(创建时必须初始化) int& ref;
3.没有缺省构造函数的类成员变量
#include <iostream>
using namespace std;
class Time
{
public:
Time(const Time& t)
{
_hour = t._hour;
_minute = t._minute;
_second = t._second;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year, int month, int day, const Time& t)
{
_year = year;
_month = month;
_day = day;
_t = t;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
const int N;//常量成员变量
int& ref;//引用类型成员变量
Time _t;//没有缺省构造函数的类成员变量
system("pause");
return 0;
}
成员变量按照声明顺序依次初始化,而非初始化列表的顺序
#include <iostream>
using namespace std;
class A
{
public:
A(int x)
:_a2(x)
,_a1(_a2)
{
cout << _a1 << endl;//随机值
cout << _a2 << endl;//10
}
private:
int _a1;
int _a2;
};
int main()
{
A a(10);
system("pause");
return 0;
}
若成员变量为非内置类型,则会调用缺省构造函数进行初始化
若成员变量为内置类型,则什么都不做
拷贝构造函数
创建对象时使用同类对象来进行初始化,拷贝构造函数是特殊的构造函数
特征:
1.拷贝构造函数其实是一个构造函数的重载
2.拷贝构造函数的参数必须使用引用传参,使用传值的方式会引发无穷递归调用
3.若未显示定义,系统会默认缺省的拷贝构造函数,缺省的拷贝构造函数会依次拷贝类成员进行初始化
#include <iostream>
using namespace std;
class Date
{
public:
Date()
{}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
Date d3 = d1;
system("pause");
return 0;
}
在类里的对象可以访问私有成员
若在类中写了拷贝构造函数就必须写构造函数,系统不会自动生成默认构造函数
若成员变量为内置类型,则什么都不做
析构函数
当一个对象的生命周期结束时,C++编译系统会自动调用一个成员函数
特征:
1.析构函数在类名前加上字符~
2.析构函数无参数无返回值
3.一个类有且只有一个析构函数。若未显示定义,则系统会自动生成缺省的析构函数
4.对象生命周期结束时,C++编译系统自动调用析构函数
5.析构函数并不是删除对象,而是做一些清理工作
在顺序表类中若不写拷贝构造和析构函数,系统自动生成的拷贝构造和析构函数无法实现
//基于动态数组的顺序表
#include <iostream>
using namespace std;
class SeqList
{
public:
//构造函数
SeqList(size_t capacity = 0)
{
if (capacity > 0)
{
_a = (int*)malloc(sizeof(int)*capacity);
_size = 0;
_capacity = capacity;
}
else
{
_a = NULL;
_size = 0;
_capacity = capacity;
}
}
//析构函数
~SeqList()
{
if (_a)
free(_a);
_a = NULL;
_size = 0;
_capacity = 0;
}
private:
int* _a;
size_t _size;
size_t _capacity;
};
int main()
{
SeqList s1(10);
SeqList s2(s1);
system("pause");
return 0;
}
若成员变量为内置类型,则什么都不做
运算符重载
特征:
1.函数名:operator+合法的运算符(重载<运算符的函数名:operator<)
2.重载运算符以后,不能改变运算符的优先级,结合性,操作数个数
5个C++不能重载的运算符:
.* :: sizeof ?: .
#include <iostream>
using namespace std;
class Date
{
public:
void show(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& d)
{
return this->_year == d._year
&& this->_month == d._month
&& this->_day == d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.show(1990, 1, 1);
Date d2;
d2.show(1990, 1, 1);
cout << d1.operator==(d2) << endl;
cout << (d1 == d2) << endl;
system("pause");
return 0;
}
若成员变量为内置类型,则什么都不做
赋值运算符重载
拷贝构造函数是创建对象,使用一个已有对象来初始化这个准备创建的对象
赋值运算符重载是对一个已存在的对象进行拷贝赋值
#include <iostream>
using namespace std;
class Date
{
public:
//构造函数
Date(int year = 2018, int month = 3, int day = 20)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{}
//赋值运算符重载
Date& operator=(const Date& d)
{
if (this != &d)//取地址
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);//拷贝构造
Date d3;
d1 = d3;//赋值运算符重载
system("pause");
return 0;
}
赋值表达式有返回值
若成员变量为内置类型,则什么都不做
const修饰成员函数
const对象可以调用const成员函数
非const对象可以调用const成员函数和非const成员函数
void show() const{}和void show(){}可以在一个类中同时存在,构成函数重载
内联函数
以inline修饰的函数,编译时C++编译器会调用内联函数的地方展开,没有函数压栈的开销,提升程序运行的效率
特征:
1.inline是一种以空间换时间的做法,省去调用函数的开销。代码很长或者含有循环,递归的函数不宜使用内联
2.inline对于编译器来说只是一个建议,编译器会自动优化。若定义为内联的函数体内含有循环,递归等,编译器优化时会忽
略内联
3.inline必须和函数定义放在一起,仅将inline放在函数声明前是不起作用的
4.定义在类内的成员函数默认定义为内联函数
*宏
优点:
增强代码可维护性(宏常量)
没有压栈,提高效率(宏函数)
缺点:
不方便调试(在预处理时调用,不用编译)
代码可读性较差,可维护性较差,容易出错(宏函数)
没有类型安全检查
#include <stdio.h>
#define Swap(a,b) do{int tmp = a; a = b; b = tmp;}while (0);//只执行一次
int main()
{
int x1 = 10;
int x2 = 100;
Swap(x1, x2);
int x3 = 5;
int x4 = 0;
Swap(x3, x4);
system("pause");
return 0;
}
若无{},则重定义了tmp
内联函数可以替换宏函数
const,枚举可以替换宏常量
友元函数
以friend声明的函数,允许在类外访问该类中的任何成员
特征:
1.友元函数不是类的成员函数
2.友元函数可以通过对象访问所有成员,包括私有和保护成员
输入输出运算符的重载的友元函数
#include <iostream>
using namespace std;
class Date
{
public:
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
Date(int year = 1990, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "-" << d._month << "-" << d._day << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
cout << "请输入年月日:" << endl;
in >> d._year;
in >> d._month;
in >> d._day;
return in;
}
int main()
{
Date d(2018, 3, 31);
cout << d;
cin >> d;
system("pause");
return 0;
}
友元类
整个类可以是另一个类的友元(单向的)
友元破坏了封装性
类的静态成员
类里面用static修饰的成员
类的静态成员不属于某个对象,属于类的所有对象
静态的成员函数无this指针,可以使用::作用域访问符直接调用静态成员函数,但不可以访问普通成员函数
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1990, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{
++_count;
}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
++_count;
}
void Show() const
{
cout << _year << "-" << _month << "-" << _day << endl;
GetCount();
}
static int GetCount()
{
//_year = 10;
//Show();
return _count;
}
private:
int _year;
int _month;
int _day;
static int _count;
};
int Date::_count = 0;
int main()
{
Date d1(2018, 4, 2);
Date d2(2018, 10, 30);
//cout<<d1._count<<endl;
//cout<<d2._count<<endl;
//cout<<Date::_count<<endl;
cout << d1.GetCount() << endl;//2
cout << d2.GetCount() << endl;//2
cout << Date::GetCount() << endl;//2
system("pause");
return 0;
}
构造拷贝赋值函数的N种调用情况
#include <iostream>
using namespace std;
class Date
{
public :
Date()
{
cout<<"Date()" <<endl;
}
Date(const Date& d)
{
cout<<"Date(const Date& d)" <<endl;
}
Date& operator=(const Date& d )
{
cout<<"Date& operator=(const Date& d)"<< endl;
return *this ;
}
~Date()
{
cout<<"~Date()" <<endl;
}
void Show()
{}
};
void fun1 (Date d)
{}
Date fun2()
{
Date ret;
return ret;
}
Date fun3()
{
return Date();
}
int main()
{
//Date().Show();//一次构造(Date()为匿名对象(临时对象),生命周期只在当前一行)
//Date d1;
//fun1(d1);//一次构造,一次拷贝
//d1 = fun2();//构造两次,拷贝一次,赋值一次
//fun1(Date());//一次构造(Date()和d均为临时变量,合二为一,减少对象创建)
//Date d = fun2();//一次构造,一次拷贝(生成临时变量,立即拷贝给b,合二为一)
//Date d = fun3();//一次构造
system("pause");
return 0;
}
#include <iostream>
using namespace std;
class Date
{
public:
Date()
{
cout << "Date()" << endl;
}
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
}
Date& operator=(const Date& d)
{
cout << "Date& operator=(const Date& d)" << endl;
return *this;
}
~Date()
{
cout << "~Date()" << endl;
}
void Show()
{}
};
void fun1(Date& d)
{}
Date& fun2()
{
Date ret;
return ret;
}
Date& fun3()
{
return Date();
}
int main()
{
//Date().Show();//一次构造
//Date d1;
//fun1(d1);//一次构造
//d1 = fun2();//构造两次,赋值一次
//fun1(Date());//一次构造
//Date d = fun2();//一次构造,一次拷贝(出函数域进行的拷贝)
//Date d = fun3();//一次构造,一次拷贝
system("pause");
return 0;
}
只有传参和传返回值会进行优化
例题:
//Test1中调用了_2_次AA的拷贝构造函数,_1_次AA的赋值运算符函数的重载
//Test2中调用了_2_次AA的拷贝构造函数,_0_次AA的赋值运算符函数的重载
//Test3中调用了_3_次AA的拷贝构造函数,_0_次AA的赋值运算符函数的重载
#include <iostream>
using namespace std;
class AA
{
AA f(AA a)
{
return a;
}
void Test1()
{
AA a1;
a1 = f(a1);
}
void Test2()
{
AA a1;
AA a2 = f(a1);
}
void Test3()
{
AA a1;
AA a2 = f(f(a1));
}
};
struct类
默认访问限定符: public