上篇
1.类的定义:
class className{
//类体
};
class 为定义类的关键字,className为类的名字。注意类结束之后的分号。
类中的元素称为类的成员,类中的数据成为类的属性或者成员变量,类中的函数称为类的方法。
2.类的两种定义方式
(1)声明和定义全部放在类中(这样编译器可能会将成员函数当成内联函数对待);
(2)声明放在.h文件中,定义放在类的实现文件.cpp中。
3.类的访问限定符以及封装
(1)访问限定符:
public,private,protected。
注意:public修饰的成员在类外可以直接被访问;
class默认private,struct默认public
(2)封装
面向对象三大特性:继承,封装,多态
封装的本质是一种管理:开放想让你访问的成员函数或者变量。
4.类的作用域
(1)类定义了一个新的作用域,类的所有成员都在类的作用域中。在类外定义成员,需要使用::作用域解析符指定成员属于那一个类域。
clss person{
public:
void printperson();
private:
char _name[20];
char _gender[3];
int _age;
};
void person::printperson()
{
//这里就是在类外,因此需要指明类域。
}
5.类的实例化
clss person{
public:
void printperson();
private:
char _name[20];
char _gender[3];
int _age;
};
int main(){
person A;//A就是person类的实例化。
return 0;
}
6.类对象的存储方式
只保存成员变量,成员函数存在在公共代码段(节省空间)。
例如:
#include<iostream>
using namespace std;
class A
{
public:
void f1(){}
private:
int _a;
};
class A2
{
public:
void f2(){}
};
//A3是空类
class A3
{
};
int main()
{
printf("A1=%d\n", sizeof(A));
printf("A1=%d\n", sizeof(A2));
printf("A1=%d\n", sizeof(A3));
return 0;
}
7.结构体内存对齐规则
(1)第一个成员在与结构体偏移量为0;
(2)其他成员需要对齐到某个数字的整数倍的地址处;
(3)对齐数=编译器默认的一个对齐数 与该成员大小的较小值。
(4)结构体的总大小为:最大对齐数的整数倍。
8.this指针
c++编译器给每个“非静态的成员的函数”增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该对象的对象),在函数体中所有成员变量的操作都是通过该指针去访问的。
(2)this指针的特性
类型为: 类* const
只能在成员函数的内部使用
this指针的本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。
this指针是成员函数第一个隐含的指针形参。
例如:
```cpp
//display是date类的成员函数
void display()//void display(Date * this)
{
}
中篇
1.构造函数
主要完成初始化工作,构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初值,并且在对象的声明周期内只调用一次。
特征如下:
(1)函数名与类名相同
(2)无返回值
(3)对象实例化时编译器自动调用对应的构造函数
(4)构造函数可以重载
#include<iostream>
using namespace std;
class Date {
public:
//构造函数
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
默认构造函数有三种:1.不写,编译器会自动生成;2.无参构造函数;3.全缺省构造函数。(默认的意思就是不需要传参。)
对于c++编译器而言,内置类型不需要初始化,自定义类型需要自己写初始化函数。
2.析构函数
对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
析构函数是特殊的成员函数。
其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值。
- 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
typedef int DataType;
class SeqList
{
public :
SeqList (int capacity = 10)//构造函数
{
_pData = (DataType*)malloc(capacity * sizeof(DataType));
assert(_pData);
_size = 0;
_capacity = capacity;
}
~SeqList()//析构函数
{
if (_pData)
{
free(_pData ); // 释放堆上的空间
_pData = NULL; // 将指针置为空
_capacity = 0;
_size = 0;
}
}
private :
int* _pData ;
size_t _size;
size_t _capacity;
};
3.拷贝构造函数
是构造函数的一个重载形式,参数只有一个且必须使用引用传参,使用传值会引发无穷递归调用。
lass Date
{
public:
//构造函数
Date(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;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);//调用拷贝构造,隐藏有this指针。拿一个已经存在的对象去构造初始化创建另一个对象
return 0;
}
注意:(1)传参的时候使用传值的话,层层传值引发对象的拷贝的递归调用。因为传值又是一种拷贝构造,一直传值一直拷贝构造,停不下来。
(2)编译器默认生成的拷贝构造完成的是浅拷贝,因此对于自定义类型,需要调用他的拷贝构造成员进行拷贝。
4.赋值运算符重载问题
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的;操作符有一个默认的形参this,限定为第一个形参。
.* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。
// bool operator==(Date* this, const Date& d2)
// 这里需要注意的是,左操作数是this指向的调用函数的对象
bool operator==(const Date& d2)
{
return _year == d2._year;
&& _month == d2._month
&& _day == d2._day;
}
这里就是一个==的重载。
Date& operator=(const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
}
这就是赋值运算符的重载。第一个参数为this指针,进行隐藏了。
这个函数在调用的时候先会调用拷贝构造。返回引用。
5.const成员
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。(保护this指针)
- const对象不可以调用非const成员函数
- 非const对象可以调用const成员函数
- const成员函数内不可以调用其它的非const成员函数
- 非const成员函数内可以调用其它的const成员函数
const修饰的成员变量,权限很小;在访问的时候遵循一个规则:权限只能缩小,不能放大。
下篇
1.再谈构造函数
初始化分为两种:一种是函数体内初始化;一种为初始化列表。
#include<iostream>
using namespace std;
class Date {
public:
//构造函数
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
这种就是函数体内初始化;
//初始化列表
//初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
#include<iostream>
using namespace std;
class Date {
public:
//构造函数 --这就是初始化列表
Date(int year = 0, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
注意:引用成员变量,const成员变量,自定义类型成员(该类没有默认构造函数)必须在初始化列表的位置进行初始化。
且成员变量在类中声明次序就是在其初始化列表中的初始化顺序。
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void Print()
{
cout<<_a1<<" "<<_a2<<endl;
}
private:
int _a2;
int _a1;
}
int main()
{
A aa(1);
aa.Print();
}
此题运行结果为 1,随机值。就是考察的初始化顺序问题。
2.explicit关键字
作用:用此关键字修饰构造函数,将会禁止但参构造函数的隐式转换。
Date(int year)
:_year(year)
{}
explicit Date(int year)
:_year(year)
{}
禁止了隐式转换。
3.static成员
static修饰成员–静态 成员;static修饰成员函数–静态成员函数
可以理解为静态成员只要突破类域就可以访问。
注:静态成员没有隐含的this指针,因此不能访问任何静态成员。
class A
{
public:
A() {++_scount;}
A(const A& t) {++_scount;}
static int GetACount() { return _scount;}
private:
static int _scount;//声明
};
int A::_scount = 0;//静态变量的定义
void TestA()
{
cout<<A::GetACount()<<endl;
A a1, a2;
A a3(a1);
cout<<A::GetACount()<<endl;
}
在此代码中,构造的匿名对象,生命周期只在这一行,声明周期结束后,调用析构函数。
因此定义一个对象要用,但是只用一行,一般采用匿名对象。
注:1.静态成员变量必须在类外定义,定义时不添加static关键字。
2.静态成员为所有类对象所共享,不属于某个具体的实例;
3.类的静态成员通过::,突破类域来访问。
4.友元函数和友元类
友元函数:可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加上friend关键字。
例如A是B的友元类,A可以访问B(私有成员),但是B不可以访问A里面的,因为是友元关系是单向的。
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声
明,声明时需要加friend关键字。
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);//声明<<是Date类的友元
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout<<d._year<<"-"<<d._month<<"-"<<d._day;
return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
_cin>>d._year;
_cin>>d._month;
_cin>>d._day;
return _cin;
}
int main()
{
Date d;
cin>>d;
cout<<d<<endl;
return 0;
}
注意:
(1)友元函数可访问类的私有和保护成员,但不是类的成员函数
(2)友元函数不能用const修饰
(3)友元函数可以在类定义的任何地方声明,不受类访问限定符限制
(4)一个函数可以是多个类的友元函数
(5)友元函数的调用与普通函数的调用和原理相同
友元类:友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
(1)友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
(2)友元关系不能传递
如果B是A的友元,C是B的友元,则不能说明C时A的友元。
class Date; // 前置声明
class Time
{
friend class Date; //声明Date类为Time类的友元类,则可以在Date类中就直接访问Time的成员变量。
5.内部类
简单来说就是class里面在class,嵌套class。
注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中
的所有成员。但是外部类不是内部类的友元。
class A
{
private:
static int k;
int h;
public:
class B
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 1;