1.什么是面向对象?
想想之前学过的c语言,c语言是面向过程的语言,不管是简单类型还是复杂类型,只是一味的调用函数,这里的调用函数就是将数据处理的过程,更加在乎的是处理逻辑和处理结果
c++是面向过程的语言,首先要说一下什么是类?什么是对象?
-
类,通俗的就讲就是一个事物的描述,描述着这一类事物的特征和动作,即成员变量和成员函数
-
对象,是类实例化出来的,这样讲可能太过于生硬,如果把类比作一栋大楼的图纸,那么对象就是根据这张图纸建造出来的大楼
-
那么面相对象编程就是,用一个类实例化出对象,然后对象执行他所拥有的动作,此外,c++还有三个重要的特点:
-
继承:书->教材->计算机类教材 ,继承可以将一个大类,逐渐具体为更具体的小类
-
封装:例如一部手机,我们在使用时,并不需要知道手机具体怎么制造,只要知道如何使用就行
-
多态: 人.看(书) & 人.看(景色) ,参数类型不一样,执行的也不一样
2.类的大小?为什么要内存对齐?内存对齐的计算?空类的计算
一个类中既有成员变量,又有成员函数,计算一个类的时候只看成员变量的大小,具体计算方法是根据内存对齐原则,如果一个类中没有成员变量,那么这个类的大小为一个字节,这一个字节是用来占位的,假设没有成员变量的类的大小是0,那么这个类就不能实例化对象,也就没办法调用类中的方法 ,成员函数并不参与类大小的计算,这些成员函数放在一个公共的位置,每个实例化的对象都能看见,在需要的时候调用就好,这是一种节省空间的方法,假如给每个对象里面都把成员函数放进去,那么这个对象就会很大,而且变大完全没有意义,并且浪费内存。
内存对齐是为了提高程序效率的一种以空间换时间的方法,虽然浪费了内存,但是提高了效率,这跟地址总线有关,假设一个台器,一次在内存上可以取四个字节,取多了会将对于的丢弃,取少了会继续取,有这么一个类:
class A {
char c;
double d;
};
在没有内存对齐时,取成员变量b就要取三次,而有内存对齐,就只取两次也能达到效果,作何理解? 先看下图
在没有内存对齐的情况下,要取到d的前三个字节,必须从c开始取,丢弃第一个字节,然后继续向后取四个字节,再取四个字节,丢弃最后三个字节,然后将取到的内容拼接
在有内存对齐的情况下,直接两次提取d的前后四个字节进行拼接,只提取了两次,提高了效率
3.类的4个默认成员函数的详细使用及细节
默认构造函数
构造函数无任何返回值,也不使用void,函数名和和类名保持一致,可以有参数,也可以没有参数
那么构造函数是用来干什么呢?构造函数会在对象创建时自动调用,如果没有在类中定义,系统会自动生成一个默认的无参的构造函数,这个构造函数什么也不干,一般的,我们都会在类中自己写构造函数,用来初始化对象,如果我们自己写了构造函数,系统就不会再自动生成。代码如下:
class people {
public:
people(int id, int age) {
_id = id;
_age = age;
}
private:
int _id;
int _age;
};
//创建对象是就可以 people p1(1001, 21);
析构函数
在构造函数名前加一个波浪线(~)就叫做析构函数,这个函数在对象销毁时调用,完成一些清理工作,代码如下:
#include<iostream>
class people {
public:
people(int id, int age) {
_id = id;
_age = age;
}
~people() {
std::cout << "~people()" << std::endl;
}
private:
int _id;
int _age;
};
int main()
{
{
people p1(1001, 21);
}
system("pause");
return 0;
}
在vs下,要使用 system("pause"); 使“黑框框”停下,所以要观察析构函数,就在对象外面包裹一对{},如果不加大括号,主函数没出就不会调用析构函数,但是想要调用,就必须走过 system("pause");,但是过了这一句,“黑框框”就会消失。
拷贝构造函数
拷贝构造也是一个构造函数,参数就是这个类的一个对象的引用,如果不在类中写拷贝构造,那么系统会自动生成一个默认拷贝构造函数,将作为参数的这个对象的内容一个字节一个字节的拷贝到调用拷贝构造的对象之中,如果在类中写了拷贝构造,那么系统就不会自动生成,而是执行类中的拷贝构造函数,
深拷贝和浅拷贝
规定一个场景,在一个类中,有一个char型的指针,构造了一个对象a1, 这个对象中的指针指向了一块堆上开辟用来存放字符串的内存,如果使用系统默认的拷贝构造函数创建一个a2,这个默认的拷贝构造函数只会将a1对象中的指针的值复制给了a2对象中的指针,让a2中的指针指向了同一块内存,这个叫做浅拷贝
如果我们自己写了一个拷贝构造函数,这个拷贝构造函数会让a2中的指针指向新开辟的一块堆上的内存,并且这块内存中的内容是从a1指向的内存中复制而来,这个叫做深拷贝
原理图如下:
赋值操作符重载
再说复制函数之前要说一下运算符重载,操作符重载是针对用户自定义类型,可以理解为类类型,为了使自定义类型能够像内置类型一样使用操作符,c++的设计者创造了操作符重载,但是有五个操作符不能重载:.*/::/sizeof/?:/.
运算符重载特征:
1. operator+合法的运算符构成函数名(重载<运算符的函数名:operator<)。
2. 重载运算符以后,不能改变运算符的优先级/结合性/操作数个
对赋值操作符重载
#include<iostream>
class people {
public:
people(int id = 0, int age = 0) {
this->_id = id;
this->_age = age;
}
people& operator= (const people& p) {
if (this != &p) {
this->_id = p._id;
this->_age = p._age;
}
return *this;
}
private:
int _id;
int _age;
};
int main()
{
int a = 10;
int b = a;
std::cout << "a = " << a << " " << "b = " << b << std::endl;
people p1(1001, 20);
people p2;
people p3;
people p4;
people p5 = p4 = p3 = p2 = p1;
system("pause");
return 0;
}
这里有一个问题,赋值操作符的重载函数一定要返回一个引用吗?
必须返回!我们都只到,内置类型支持像p5一样的连续赋值,如果不返回*this的引用,p5的赋值就会报错
在这里简单的解释一下this指针
这是在对象调用成员函数时隐藏的一个指针,这个指针指向调用该成员函数的对象,以上面的people类为例,在p1调用构造函数时,看起来是这样的
实际上编译器把这二个过程解释为了
但是这个this不能显式的写出来,但是确实存在,抢占了左边第一个参数,并且可以使用。