一、类的定义:
如下代码:
class people
{
char* name;
char* sex;
int age;
};
class就是类的关键字。people就是这个类的名字,{}为类的主体,里面有成员,变量叫做成员变量,函数叫做成员函数。 c语言中的struct也可以作为类的关键字。只不过在c中最好使用c的class写法。
struct people
{
void print()
{
cout << "hello world\n";
}
int age;
char name[20];
};
类的定义有两种形式: 一种是成员函数的声明和定义都在类的里面。 另一种是类的里面是成员函数的声明,在另一个文件中实现类的定义。
二、类的限定符
类的限定符有:public(公有) protected(保护) private(私有)
公有是别人是可以访问的, 私有为别人无法访问。 对于class的类默认访问权限是private;对于struct的类默认访问权限是public。
struct people
{
int age;
char name[20];
};
class p
{
char* name;
char* sex;
int age;
};
int main()
{
people a;
a.age;//这个可以访问
//p b;
//b.age;//这个就不可以访问
return 0;
}
我们在实现类的时候,想给别人使用的就用public限定成共有的。不想让别人使用的就限制成私有的。
class p
{
public:
void print()
{
cout << "hello world\n";
}
private:
char* name;
char* sex;
int age;
};
三、类的作用域
类定义了新的定义域,里面的成员都属于该作用域。当在类的外面定义成员函数的时候,需要说明该成员的作用域。
class p
{
public:
void print();
private:
char* name;
char* sex;
int age;
};
void p::print()
{
cout << "hello world\n";
}
四、类的实例化
实现一个类,当这个类没有创建对象的时候,它在内存中是没有开辟空间的。 类的实例化也就是用类创建对象。 类的大小只需要计算成员变量的大小(计算方法和c语言的一样)。 当类为空的时候,大小为1字节,只是为了占位。
class Test
{};
int main()
{
std::cout << sizeof(Test) <<std::endl;
return 0;
}
此时输出为:1;
而成员函数是放在公共代码区的,并不是在成员里面。
class P
{
public:
void Print()
{
cout << "haha" << endl;
}
};
int main()
{
P* ptr = nullptr;
ptr->Print();
return 0;
}
在第13行的时候,编译的时候根本就没有解引用去找成员,而是直接去公共代码区去找的该函数的地址。
五、this指针
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << ' '<<_month <<' '<< _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date a, b;
a.Init(1, 1, 1);
b.Init(2, 2, 2);
a.Print();
b.Print();
return 0;
}
输出:1 1 1 2 2 2
程序是怎么判断Init,Print是各自属于哪一个对象的? 其实有一个this指针,C++编译器在非静态成员函数增加了隐形的指针参数,调用函数的时候其实把对象的地址传递给了this指针。 this指针是编译的时候程序自己添加的,不需要我们自己手动添加。
this指针的特性:
1.this指针的类型是类的类型*const。
2.this不能在参数中显示,但是可以在函数体中的成员变量中可以使用。
3.this是成员函数的第一个隐形参数,存在栈区,因为是形参,具体存在哪里取决于编译器,不需要用户自己传递。
看下面这个代码:Print1就可以正常的运行,而Print2并不能,就是因为Print2需要对空指针解引用。编译的时候不会出错,因为不会去类中去找这个函数。
class Date
{
public:
void Print1()
{
cout << "Print1()" << endl;
}
void Print2()
{
cout << _year << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date* a = nullptr;
a->Print1();
a->Print2();
return 0;
}
六、默认构造函数
6.1 构造函数
构造函数并不是创建一个函数,它是一种特殊的函数,给对象初始化用的。 构造函数的形式:
类名(参数)
{};
构造函数也可以重构,构造函数一般写在类里面实现形成内联函数。 当没有自己写构造函数的时候,会调用默认的构造函数,默认的构造函数只对自定义类型进行处理(调用它的构造函数),对内置类型不做处理。
内置类型:int double char 指针等等
自定义类型:类 等等
默认构造函数:全缺省,无参,自动生成的默认构造函数 因为不对内置类型处理,所以C++11又进行了弥补,在类的成员又加了一个默认值。
6.2 析构函数
析构函数的形式:
~类名()
{};
析构函数是释放类成员的资源。不是销毁对象。 析构函数可以自己写,当没有自己实现的时候会调用默认析构函数,默认析构函数对内置类型不做处理,对自定义类型会调用它的析构函数。 析构的顺序是先构造的后析构,后构造的先析构,局部先析构,全局/静态后析构。
6.3 拷贝构造函数
拷贝构造函数的形式:
类名(const类名&对象)
{};
如果你创建的对象想直接是另一个对象的值,就可以使用拷贝构造。
class Date
{
public:
Date(int year = 1, 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 a(2022, 8, 11);
Date b(a);
Date c = a;
return 0;
}
第26,27行的是同一个意思,都是拷贝构造,不能把第27个看作是赋值。 关于为什么拷贝构造函数的参数是引用呢? 是为了放在死拷贝。如果是传值拷贝,在传参的时候就要拷贝,然后就业调用这个函数,调用这个函数又需要传值拷贝,最后还是要调用这个函数…无穷无尽。而传引用就避免了这个情况。 如果没有自己写拷贝构造函数,会调用默认构造函数,默认构造函数对内置类型进行值拷贝,对自定义类型会调用它的拷贝构造函数。
七、运算符重载
让对象也可以直接运用运算符, 运算符重载的形式:
返回类型operator运算符(参数)
{};
.* :: sizeof ?: .这5个运算符不能重载
7.1 赋值运算符重载
以日期类为例:
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
弄返回值是为了应对多次赋值的情况,引用左返回值和参数的原因是避免拷贝构造。 即使没有显式实现赋值运算符重载,也会生成默认的,其和拷贝构造一样,内置类型值拷贝,自定义类型调用它的赋值重载。
7.2 前置++/后置++重载
前置后置怎么实现呢? 前置,无参数 后置++,有一个int参数,只是为了去吧,没有什么实际意义。
//前置
Date& operator++()
{
_day++;
return *this;
}
//后置
Date operator++(int)
{
Date ret = *this;
_day++;
return ret;
}
八、const成员
我们知道const修饰的不能改变,当const修饰的对象时,该对象的地址的类型是const 类*,而this指针的类型是类* const,会导致类型不匹配,所以此时要在类的成员函数后面加上一个const。 例如日期类:
class Date
{
public:
bool operator==(const Date& d)
{
return _year == d._year && _month == d._month && _day == d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date a;
const Date b;
b == a;//这里就会报错
return 0;
}
因为此时传过去的类型是const Date*,而this的类型是Date* const。 这样处理就可以解决这种问题了bool operator==(const Date& d)const。 只要是不改变this指针指向的内容都可以加上const。
九、&取地址操作符重载
这种操作符默认会生成,不需要自己写:
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
十、初始化列表
为什么要引用初始化列表?因为对于一些引用类型,修饰的类型const,自定义类型(特别是类中没有默认的构造函数)的时候。单靠在构造函数内部实现是不可能完成的。而初始化列表就可以解决这些问题。
它的形式是在函数后面加上:,有多个变量的时候以,分割。:类的成员(初始化), ,
初始化列表可以理解为成员变量定义的地方;成员变量声明的顺序是初始化的顺序。
class Time
{
public:
Time(int hour)//此处非默认构造函数
{
_hour = hour;
}
private:
int _hour;
};
class Date
{
public:
Date(int year)
:_year(year)
, _a(0)//该成员必须用初始化列表进行初始化
{}
private:
int _year;
Time _a;
};
class f
{
public:
f()
:B(1)
,A(B)
{
cout << A << ' ' << B << endl;
}
private:
int A;
int B;
};
十一、explicit关键字
取消对隐式类型的转换,对与单参数(下面的这种)或者第一个参数没有初始值(如:Date (int year, int month=0,int day=0)) 例:
class Date
{
public:
explicit Date(int year=1)
:_year(year)
{}
private:
int _year;
};
int main()
{
Date a;
a = 2022;//错误
return 0;
}
这里的2022需要进行隐式转换,转换成Date类型,然后进行赋值操作。然而用explicit关键字可以阻止这中转换。此时那种转换就不正确了。
十二、static
12.1 static成员变量
static成员变量是静态变量,属于所有对象共享资源,当然它也受限定符的限定。静态的变量不能在构造函数(初始化列表)中进行定义。因为静态的木有this指针,它在全局进行定义。非静态的还是用构造函数进行初始。
class Date
{
public:
Date()
:_month(1)
{}
private:
static int _year;
int _month;
};
int Date::_year = 1;
12.2 static成员函数
static成员函数也是所有对象共享的成员,在此函数里面只能访问静态成员,因为没有this指针。该成员函数的访问有两种方式:类名::函数,对象.函数
class Date
{
public:
Date()
:_month(1)
{}
static void Print()
{
cout << _year;
}
private:
static int _year;
int _month;
};
int Date::_year = 1;
int main()
{
Date a;
a.Print();
Date::Print();
return 0;
}
十三、友元
关键字friend
13.1 友元函数
在<<运算符重载的时候,<<左边是流,右边是对象,在重载的时候因为第一个参数为this指针,会导致类型不匹配。除了写成全局的函数外,还可以用友元函数进行处理,设置成友元的时候,就没有了this指针了,就可以改变第一个参数了(即不然他为this指针)。 友元函数不属于任何类的成员,和普通的函数几乎没有区别。 类的友元函数可以访问私有成员。声明在类的类别,在类的外面进行定义。
下面这个是<<的运算符重载。
class Date
{
friend ostream& operator<<(ostream& a, Date& b);
public:
Date()
:_year(1)
,_month(1)
{}
private:
int _year;
int _month;
};
ostream& operator<<(ostream& a, Date& b)
{
cout << b._year << ' ' << b._month;
return a;
}
13.2 友元类
假如A是B的友元类,在B中可以访问A的私有成员。
class B
{
friend class A;
private:
int _b = 2;
};
class A
{
public:
void Print(B b)
{
cout << _a<< ' ' <<b._b << endl;
}
private:
int _a=1;
};
int main()
{
A().Print(B());//匿名对象
return 0;
}
十四、 内部类
内部类就是在一个类的里面再定义一个类,这就叫内部类。内部类是外部类的友元,即在内部类中可以访问外部类的私有成员。
class A
{
class B
{
public:
B()
:_b(0){}
void Print(A a)
{
cout << a._a;
}
private:
int _b;
};
public:
A()
:_a(0){}
private:
int _a;
};