目录
1. 面向过程和面向对象初步认识
2. 类的引入
3. 类的定义
4. 类的访问限定符及封装
4.1 访问限定符
4.2 封装
5. 类的作用域
6. 类的实例化
7. 类对象模型
8. this指针
8.1 this指针的引出
8.2 this指针的特性
没有坚持的努力,本质上并没有多大意义!
1. 面向过程和面向对象初步认识
C 语言是 面向过程 的, 关注 的是 过程 ,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++ 是 基于面向对象 的, 关注 的是 对象 ,将一件事情拆分成不同的对象,靠对象之间的交互完成。
2. 类的引入
C 语言中,结构体中只能定义变量,在 C++ 中,结构体内不仅可以定义变量,也可以定义函数。就升级成了类。(类的关键字是class,类定义对象)
(1)结构体名称可以做类型
(2)结构体可以定义函数
class ListNode
{int _val;//struct ListNode* next;//c语言的方式,C++也兼容ListNode* _next;//C++的方式
};//因为struct被升级,结构体名称可以作类型
//这里的listNode我们叫做对象
命名规则建议:C++类的成员我们习惯在前面加一个_(来区分类的对象和别的参数)
3. 类的定义
class className
{// 类体:由成员函数和成员变量组成}; // 一定要注意后面的分号
class 为 定义类的 关键字, ClassName 为类的名字, {} 中为类的主体,注意 类定义结束时后面 分号 。
类中的元素称为 类的成员: 类中的 数据 称为 类的属性 或者 成员变量 ; 类中的 函数 称为 类的方法 或者 成员函数 。
类的两种定义方式:
1. 声明和定义全部放在类体中,需要注意:成员函数如果 在类中定义 ,编译器可能会将其当成 内联函数 处理。
2. 声明放在.h 文件中,类的定义放在 .cpp 文件中
一般情况下,更期望采用第二种方式。实际中,一般情况下,短小函数可以在类里面定义,长一点函数声明和定义分开。
4. 类的访问限定符及封装
4.1 访问限定符
C++ 实现封装的方式: 用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其 接口提供给外部的用户使用 。
class Date
{
public:void Init(int year){_year = year;}
private:int _year;
};
访问限定符:public、private、protected1. public 修饰的成员在类外可以直接被访问2. protected 和 private 修饰的成员在类外不能直接被访问 ( 此处 protected 和 private 是类似的 )3. 访问权限 作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止4. 如果后面没有访问限定符,作用域就到 } 即类结束。5. class 的默认访问权限为 private , struct 为 public( 因为 struct 要兼容 C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
问题:C++中struct和class的区别是什么?解答: C++ 需要兼容 C 语言,所以 C++ 中 struct 可以当成结构体使用。另外 C++ 中 struct 还可以用来定义类。和class定义类是一样的,区别是 struct 定义的类默认访问权限是 public , class 定义的类默认访问权限是private。注意:在继承和模板参数列表位置, struct 和 class 也有区别.
4.2 封装
【面试题】 面向对象的三大特性: 封装、继承、多态 。
在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行 交互。
注意:(1)C语言数据和方法是分离的;而C++数据和方法封装到类里面,想让别人访问的部分设成公有,不想让别人访问的设成私有。
(2)一般情况下,类的成员数据都是私有或者保护。想给别人访问的函数是共有的,不想让别人访问的函数是私有或者保护。
(3)封装本质上是一种管理。
5. 类的作用域
类定义了一个新的作用域 ,类的所有成员都在类的作用域中 。 在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。(两个类里面可以有一样的变量和函数,因为类定义了一个新的作用域)
class Person
{
public:void PrintPersonInfo();
private:char _name[20];char _gender[3];int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{cout << _name << " "<< _gender << " " << _age << endl;
}
//此时,这个函数的内容,相当于在类里面,所以可以用类中的对象(私有的也可以)
(1)在类里面定义的函数默认是inline函数。实际中,一般情况下,短小函数可以在类里面定义,长一点函数声明和定义分开。
(2)类成员变量仅仅只是声明。【声明没有开辟空间】
6. 类的实例化
用类类型创建对象的过程,称为类的实例化
1. 类只是 一个 模型 一样的东西,限定了类有哪些成员,定义出一个类 并没有分配实际的内存空间 来存储它
2. 一个类可以实例化出多个对象, 实例化出的对象 占用实际的物理空间,存储类成员变量
int main()
{Person._age = 100; // 编译失败:error C2059: 语法错误:“.”return 0;
}
Person 类是没有空间的,只有 Person 类实例化出的对象才有具体的年龄。
int main()
{Person man;//这里才给类定义空间man._age = 100; return 0;
}//这样才对
7. 类对象模型
类对象的存储方式: 只保存成员变量,成员 函数存放在公共的代码段
类中仅有成员函数和什么也没有:内存为:1byte【分配一字节,占位,表示类存在过】
结论:一个类的大小,实际就是该类中 ”成员变量”之和 ,当然也要进行内存对齐,注意空类的大小,空类比 较特殊,编译器给了空类一个字节来唯一标识这个类。
知识点复习:结构体内存对齐1. 第一个成员在与结构体偏移量为 0 的地址处。2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS 中默认的对齐数为 83. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
【面试题】1. 结构体怎么对齐? 为什么要进行内存对齐?2. 如何让结构体按照指定的对齐参数进行对齐?3. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景 。
8. this指针
8.1 this指针的引出
class Date
{
public:void Display(){cout << _year << "-" << _month << "-" << _day << endl;}void SetDate(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year; // 年int _month; // 月int _day; // 日
};
int main()
{Date d1, d2;d1.SetDate(2018, 5, 1);d2.SetDate(2018, 7, 1);d1.Printf();d2.Printf();return 0;
}
Date 类中有 SetDate 与Display 两个成员函数,函数体中没有关于不同对象的区分,那当 s1 调用 SetDate 函数时,该函数是如何知道应该设置s1 对象,而不是设置 s2 对象呢?【函数都在公共区,类的变量又不能访问,仅仅是一个声明】
C++ 中通过引入 this 指针解决该问题,即: C++ 编译器给每个 “ 非静态的成员函数 “ 增加了一个隐藏的指针参 数,让该指针指向当前对象 ( 函数运行时调用该函数的对象 ) ,在函数体中所有 “ 成员变量 ” 的操作,都是通过该 指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成 。
8.2 this指针的特性
以上代码:(1)d1和d2地址,和分别对应的this是一致的。
(2)this不能修改,this指向的变量可以修改。
1. this 指针的类型:类类型 * const ,即成员函数中,不能给 this 指针赋值,不能修改。2. 只能在 “ 成员函数 ” 的内部使用3. this 指针本质上是 “ 成员函数 ” 的形参 ,当对象调用成员函数时,将对象地址作为实参传递给 this 形参。所以对象中不存储 this 指针 。4. this 指针是 “ 成员函数 ” 第一个隐含的指针形参,一般情况由编译器通过 ecx 寄存器自动传递,不需要用 户传递
【面试题】1. this 指针存在哪里?一般在栈区,有些编译器会使用寄存器优化,存放在寄存器。2. this 指针可以为空吗?可以的,但是不能解引用变量,可以是函数(函数在公共区域)
class A
{
public:void Show(){cout << "Show()" << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->Show();//这个不是解引用,因为函数在公共区域,并不是在空指针的位置,所以不会报错}//正常运行
class A
{
public:void Show(){cout << "Show()" << endl;}int _a;
};int main()
{A* p = nullptr;p->Show();//这个不是解引用,因为函数在公共区域,并不是在空指针的位置,所以不会报错p->_a = 0;//这个是解引用,在类里,空指针,所以就会报错
}//运行崩溃
class A
{
public:void PrintA(){cout << _a << endl;//在这里进行空指针的解引用,报错}
private:int _a;
};int main()
{A* p = nullptr;p->PrintA();
}
//运行崩溃