- C++是基于面向对象的,关注的是对象。
- 类的格式是class Name{};
- C++中class和struct的区别:C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外,C++中可以用来定义类。和class定义类是一样的。区别是struct中的成员函数和成员变量默认访问权限是public,class中的成员函数和成员变量默认访问权限是private。
文章目录
一、类的访问限定符
- 访问权限作用域从访问限定符出现的位置开始直到下一个访问限定符出现时为止。
- class中成员函数和成员变量的默认访问权限是private。
1.public
- 访问限定符public是公有的。
- public修饰的成员变量和成员函数在类外可以直接被访问。
2.private
- 访问限定符private是私有的。
- private修饰的成员变量和成员函数在类外不能直接被访问。
3.protected
- 访问限定符protected是保护的。
- protected修饰的成员变量和成员函数在类外不能直接被访问。
二、面向对象的三大特征
- 面向对象语言(不仅是C++,Java也是)的三大特征是封装、继承、多态。
1.封装
- 封装是指将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
- 封装的本质是管理。
- C++通过类,将一个对象的属性与行为结合在一起,使其更符合人们对于一件事物的认知,将属于该对象的所有东西打包在一起;通过访问限定符选择性的将其部分功能开放出来与其他对象进行交互,而对于对象内部的一些实现细节,外部用户不需要知道。
2.继承
- 继承是面向对象程序设计使代码可以使用的最重要手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称为派生类。继承呈现了面向对象程序设计的层次结构。继承是类设计层次的复用。
- 具体实现和解释后面再说。
3.多态
- 多态是在不同继承关系的类对象中,去调用同一函数,产生了不同的行为。
- 具体实现和解释后面再说。
三、类对象的大小
- 类对象的大小计算只计算成员变量大小,不计算成员函数的大小。因为一个类实例化的对象具有相同的成员函数,但具有不同的成员变量。成员函数存放在公共的代码段,对象中只保存成员变量。
#include <iostream>
class A1
{
public:
void f1()
{}
private:
int _a1;
char _a2;
short _a3;
};
class A2
{
public:
void f1()
{}
};
class A3
{
};
int main()
{
int a1 = sizeof(A1);
int a2 = sizeof(A2);
int a3 = sizeof(A3);
std::cout << "A1: " << a1 << ", " << "A2: " << a2 << ", " << "A3: " << a3 << std::endl;
return 0;
}
- 一个类的大小,实际上就是类中的成员变量的大小,当然要进行内存对齐(内存对齐具体方法请看C语言之数据类型以及数据存储(包括大小端、截断与整型提升)
)。 - 空类的大小:编译器给了一个字节来唯一标识这个类。如上述代码中的A3类就是空类,其大小为一个字节。
四、this指针
- C++编译器给每个成员函数增加了一个隐藏的指针参数,让该指针指向当前对象即函数运行时调用该函数的对象,在函数体中所有成员变量的操作都是通过该指针去访问的。只不过所有的操作对用户都是透明的,即用户不需要传递,编译器自动完成。该指针就是this指针。
- this指针类型是:[类类型]* const
- this指针只能在成员函数内部使用。
- this指针本质是一个成员函数的形参,是对象调用成员函数时,将对象的地址作为实参传送给this指针形参。所以对象中不存储this指针。
- this指针是成员函数的第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
- this指针一般存在栈内,但在vs编译器中存储在ecx寄存器中。
- this指针可以为空。
#include <iostream>
class A
{
public:
void f1()
{}
void print()
{
std::cout << this << std::endl;
}
private:
int _a1;
char _a2;
short _a3;
};
int main()
{
A* a = NULL;
a->print();
return 0;
}
五、6个默认成员函数
1.构造函数
- 构造函数主要任务不是开空间创造对象,而是初始化对象,是特殊的成员函数。
- 构造函数的特征:(① 函数名和类名相同。② 无返回值。③ 对象实例化时,编译器会自动调用构造函数。④ 构造函数可以重载。⑤ 若类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数。一旦用户显示定义,则编译器不会自动生成。⑥ 默认构造函数有:无参的构造函数、全缺省的构造函数和没写时编译器默认自动生成的构造函数。)
- 为什么默认的构造函数只能有一个:因为默认构造函数都是无参的,如果有超过一个,则传参时会产生二义性。
class Date1
{
public:
/*
Date1(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
*/
//用初始化列表
Date1(int year = 1900, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
class Date2
{
public:
Date2(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year = 1900;
int _month = 1;
int _day = 1;
};
- 如果构造函数是默认构造函数,1.如果成员变量是自定义类型,则调用其构造函数进行初始化。2.如果成员变量是内置类型,则调用其缺省值进行初始化。
- 初始化列表:每个成员变量只能在初始化列表中只能出现一次(初始化只能初始化一次)。
- 类中包含以下成员变量(引用成员变量,const成员变量,类类型成员变量并且该类没有默认构造函数),必须放在初始化列表中进行初始化。
- 成员变量在类中的声明次序就是初始化列表中初始化顺序。
2.析构函数
- 对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
- 析构函数的特征:(① 析构函数是类名前面加字符 ~ 。② 无参数、无返回值。③ 一个类只有一个析构函数,若未显示定义时则编译器会自动生成默认的析构函数。④ 对象在生命周期结束时,C++编译器会自动调用析构函数。)
class A
{
public:
~A();
};
- 如果析构函数是默认析构函数,1.如果成员变量是自定义类型,则调用其析构函数进行析构。2.如果成员变量是内置类型,则不做处理。
3.拷贝构造函数
- 拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般用const修饰),在用已存在的类类型对象创建对象时用编译器自动调用。
- 拷贝构造函数的特征:(① 拷贝构造函数是构造函数的一个重载形式。② 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。)
#include <iostream>
class Date
{
public:
Date1(int year = 1900, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void print()
{
std::cout << this->_year << "-" << this->_month << "-" << this->_day << std::endl;
}
~Date();
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2020, 5, 4);
Date d2(d1);
d1.print();
d2.print();
return 0;
}
- 如果拷贝构造函数是默认拷贝构造函数,1.对自定义类型,则调用其拷贝构造函数进行拷贝构造。2.对内置类型,直接拷贝,将值一个一个拷贝(浅拷贝)。
4.赋值运算符重载
(1)运算符重载
- “.*”、“::”、“sizeof”、“?:”、“.”这个五个运算符不能重载。
- 函数名字:关键字operator后面接需要重载的运算符符号。
- 函数原型:【返回值类型 】operator 【操作符】(【参数列表】)
- 作为类成员的重载函数时,其形参比操作数数目少1,成员函数的操作符有一个默认的形参this限定为第一个形参。
(2)赋值运算符重载
#include <iostream>
class Date
{
public:
Date1(int year = 1900, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)//出了作用域this没有被销毁,所以引用返回
{
if (this != &d)
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
return *this;
}
void print()
{
std::cout << this->_year << "-" << this->_month << "-" << this->_day << std::endl;
}
~Date();
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2020, 5, 4);
Date d2(2019, 4, 3);
d1.print();
d1 = d2;//赋值
d1.print();
return 0;
}
(3)赋值和拷贝构造
void f1(T x)
{}
int main()
{
//此时x对t1进行拷贝构造,x不存在,要创建要初始化后再拷贝。
T t1;
f1(t1);
//此时是赋值,因为要先创建t2,t2已存在
T t2;
t2 = f1();
//此时是拷贝构造,表达式是从右边开始读
T t3 = f1();
}
5.取地址操作符重载
- 取地址操作符重载该成员函数一般不需要自己定义,编译器会默认生成。
- 只有特殊情况,才需要重载,比如想获取指定内容。
#include <iostream>
class Date
{
public:
Date1(int year = 1900, int month = 1, int day = 1)
:_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;
}
//取地址操作符重载
Date* operator&()
{
return this;
}
~Date();
private:
int _year;
int _month;
int _day;
};
6.const取地址操作符重载
- const取地址操作符重载该成员函数一般不需要自己定义,编译器会默认生成。
- 只有特殊情况,才需要重载,比如想获取指定内容。
#include <iostream>
class Date
{
public:
Date1(int year = 1900, int month = 1, int day = 1)
:_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;
}
Date* operator&()
{
return this;
}
//const取地址操作符重载
//第一个const是返回值的const 第二个const是修饰*this(即本质是修饰成员变量)
const Date* operator&()const
{
return this;
}
~Date();
private:
int _year;
int _month;
int _day;
};
六、const成员
- 将const修饰的类成员函数称为const成员函数,const修饰类成员函数,实际修饰该成员函数的隐藏的this指针,表明在该成员函数中不能对类的任何成员进行修改。
七、static成员
(1)
//计算程序构造了多少个类对象。
#include <iostream>
class T
{
public:
T()
{
++_count;
}
T(const T& t)
{
++_count;
}
static void getCount()//静态成员函数中没有this指针
{
std::cout << _count << std::endl;
//_a = 0;//不可以,因为没有隐含的this指针,private里只有声明_a没有定义。
}
void func()
{}
private:
int _a = 0;//只是声明,不是定义,给的是缺省值。
static int _count;
};
int T::_count = 0;
int main()
{
T t;
for (int i = 0; i < 10; ++i)
T t1(t);
t.getCount();
T::getCount();//也可以
t.func();
//不可以T::func();
return 0;
}
- 构造的每个对象照片那个都有不同的成员变量_a
- 构造的所有对象共享一个共同的_count。(_count属于每个对象同时属于类)
- static成员变量一定要在类外进行初始化。
- static成员函数调用时也可以T::getCount()
(2)用C++计算1+2+3+……N
条件:
1.不使用任何循环语句。(for,while,goto等等)
2.不使用任何条件语句。(if,?:等等)
3.不使用*/等运算,只使用±运算
class A
{
public:
A()
{
++_count;
_sum += _count;
}
A(const A& a)
{
++_count;
}
static void getTheCount()
{
cout << _count << endl;
}
static void getTheSum()
{
cout << _sum << endl;
}
private:
static int _count;
static int _sum;
};
int A::_count = 0;
int A::_sum = 0;
int main()
{
int n = 0;
cin >> n;
A* a = new A[n];
a->getTheSum();
return 0;
}
八、友元函数
(1)友元函数:可以直接访问类的私有成员,定义在类外部的普通函数,不属于类。
#include <iostream>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
Date(int year = 1900, 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)
{
in >> d._year;
in >> d._month;
in >> d._day;
return in;
}
int main()
{
Date d;
cin >> d;
cout << d;
return 0;
}
- 友元函数可以访问类的私有成员,但不是类的成员函数。
- 友元函数不能用const修饰。
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
- 一个函数可以是多个类的友元函数。
- 友元函数的调用与普通函数原理相同。
(2)友元类
class Date;//必须有前置声明
class Time
{
//声明日期类为时间类的友元类,则日期类可以直接访问时间类中的私有成员变量。
friend class Date;
public:
private:
int _hour;
int _minute;
int _second;
};
class Date
{
};
九、内部类
- 只有A会用B的数据,将A定义成B的内部类,此时默认B类是A类的友元类,B类可以直接访问A类私有数据。
- 内部类可以定义在外部类的public、private和protected都是可以的。
- 内部类可以直接访问外部类中的枚举成员、static成员,不需要外部类的对象/类名。
- sizeof(外部类) = 外部类。(和内部类没有关系)