一、C++入门
7. 内联函数
上次我们谈论了内联函数的作用,就是将函数给展开,并不去创建栈帧,难道内联函数就是非常非常完美吗?当然不是!内联函数会在每个地方都展开,那么假如有个内联函数代码长度为100行,被调用了10000次,则最后代码会多 100 * 10000 行代码,会非常膨胀,若是采用调用函数的方法,者只会多 100 + 10000 行,代码量大大减少。所以一般只给小函数,频繁调用的函数搞成内联函数。
inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用。
缺陷:可能会使目标文件变大。
优势:少了调用开销,提高程序运行效率。
8. auto
auto的意思就是自动推导类型。随着程序越来越复杂,程序中用到的类型也越来越复杂,有的难以拼写,于是就有了auto这个关键字来帮助你推导类型,用它来定义变量对右值是有要求的,且必须初始化。
auto的用法:
#include<iostream> using namespace std; void func(int a, int b) { // ... } int main() { void(*pf1)(int, int) = func; auto pf2 = func; cout << typeid(pf1).name() << endl << typeid(pf2).name() << endl; return 0; }
auto可以使难以理解的类型变得容易使用和理解。
#include<iostream> #include<map> #include<string> //using namespace std; int main() { std::map<std::string, std::string> dict; std::map<std::string, std::string> ::iterator it = dict.begin(); auto it1 = dict.begin(); return 0; }
auto可以使长类型变得更加方便书写。
9. 基于范围的for循环
#include<iostream> using namespace std; int main() { int array[] = { 1,2,3,4,5 }; // 依次取数组中的值赋值给e,自动迭代,自动判断结束 for (auto e : array) { cout << e << ' '; } return 0; }
范围for是一个非常新颖好用的方法,能够使遍历数组变得更加简单。此时,这里是将数组中的值赋值给e,改变e并不会改变数组中的值,我要是想要改变数组中的值该怎么办?
#include<iostream> using namespace std; int main() { int array[] = { 1,2,3,4,5 }; for (auto& e : array) { e *= 2; } for (auto e : array) { cout << e << ' '; } return 0; }
当然是引用啊,此时e就不是拷贝了,而是数组中的值的别名。
10. 指针空值nullptr
在C++/C语言当中,NULL本质上是就是被宏定义的0。而C++又有函数重载的功能,当出现一下情况时:
#include<iostream> using namespace std; void func(int i) { cout << "void func(int i);" << endl; } void func(int* p) { cout << "void func(int* p);" << endl; } int main() { func(0); func(NULL); return 0; }
结果则是:
两种调用都匹配的是第一个,这出问题了呀。所以这个宏定义的 NULL不是很好,会产生歧义。在不去强制类型转换的情况下,都是会产生匹配有歧义的状况。于是后面更新的C++语法当中,增加了 nullptr 这个新关键字。
为了提高代码的健壮性,在后续表示指针空值时建议最好使用 nullptr 。
二、类和对象
1. 面向过程和面向对象初步认识
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
2. 类的引入
在C语言当中,我们的结构体里数据与函数互相分开的,需要额外定义函数才能改变结构体里面的数据的值。而C++兼容C,C++不仅可以使用C语言的 struct ,在此基础上,还将 struct 给升级成了类。使得 struct 里也可以定义函数。但是在C++中更喜欢用 class。
#include<iostream> using namespace std; struct Stack { // 定义变量 int a; int size; int capacity; // 定义函数 int func(int i) { // ... } }; int main() { Stack st1; st1.func(1); return 0; }
类里面的变量叫做成员变量,类里面的函数叫做成员函数。
3. 类的定义
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数。
在对类的成员变量进行命名时,我们有个建议:
#include<iostream> using namespace std; class Date { public: void Init(int year, int month, int day) { // 这里是不是看的非常迷? year = year; month = month; day = day; // } private: int year; int month; int day; }; int main() { Date d1; d1.Init(2222, 2, 22); return 0; }
在类中的Init函数中,由于成员变量与形参名一样,容易混淆,于是我们建议在成员变量前面加上一个 ‘ - ’ 用来区分。就像这样:
#include<iostream> using namespace std; class Date { public: void Init(int year, int month, int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; int main() { Date d1; d1.Init(2222, 2, 22); return 0; }
这样就清晰明了多了。注意:这只是习惯,具体改动看需求。细心的小伙伴们可能已经发现了,我在类里面加了两个单词,一个是 public ,一个是 private 。要是想知道这是什么,请看下一点。
4. 类的访问限定符及封装
C++提供了三个访问限定符:1是public(共有),2是private(私有),3是protected(保护)。
4.1 访问限定符说明
- public修饰的成员在类外可以直接被访问。
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)。
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。
- 如果后面没有访问限定符,作用域就到 } 即类结束。
- class的默认访问权限为private,struct为public(因为struct要兼容C)。
总而言之,public(共有)就是成员在类外和类内都可以直接访问,而protected(保护)和private(私有)只能在类内访问。在现阶段,protected(保护)和private(私有)没有任何区别。
4.2 封装
面向对象的三大特性:封装、继承、多态。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
==封装本质上是一种 管理 ,让用户更方便使用类。==以免用户使用不当,去修改或者访问不该他访问的数据。
5. 类的作用域
一般情况下,我们不希望别人可以直接访问到我的成员变量,只希望通过成员函数来修改成员变量,所以成员变量一般都是私有的,成员函数一般都是共有的。我们来将类给声明定义分离一下:
head.h 头文件:
#pragma once #include<iostream> using namespace std; // 凡是被{}括起来的空间就是一块域 class Date { public: void Init(int year, int month, int day); private: int _year; int _month; int _day; };
test.cpp源文件:
#include"head.h" // 注意,Init函数处于Date域当中,全局找不到该函数 void Date::Init(int year, int month, int day) { _year = year; _month = month; _day = day; }
test01.cpp源文件:
#include"head.h" int main() { Date d1; d1.Init(2222, 2, 22); return 0; }
由此可见,将类的声明和定义分离后,必须要在相对应里的域找到它。
6. 类的实例化
用类类型创建对象的过程,称为类的实例化。
类的成员变量是声明还是定义?答案是声明,定义是需要开空间的,然而这里并没有开空间,所以是声明。那在哪里定义的呢?答案是实例化一个对象的时候会定义。
#include<iostream> using namespace std; class Date { public: void Init(int year, int month, int day) { _year = year; _month = month; _day = day; } private: // 这是声明 int _year; int _month; int _day; }; int main() { // 实例化对象时开空间 Date d1; d1.Init(2222, 2, 22); return 0; }
7. 类对象的大小
类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的大小?
#include<iostream> using namespace std; class Date { public: void Init(int year, int month, int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; int main() { Date d1; cout << sizeof(d1); return 0; }
结果是12,说明类所占的空间只有成员变量,那成员函数的空间去哪了?其实也容易想到,在我们给这个类实例化多个对象的时候,每个对象的成员变量不一样能够理解,但是成员函数是公共的啊,总不能每个成员里面都含有 “独属于自己的成员函数” 吧,这也太浪费空间了。所以说成员函数肯定是在一篇公共区域的,具体在哪呢?是公共代码区。接下来看看另一种情况:
#include<iostream> using namespace std; class Date {}; int main() { Date d1; cout << sizeof(d1); return 0; }
当类里面什么都没有时,此时类的大小又是多少呢?是 0 吗?
答案是 1,为啥嘞?其实是,当类里什么都没有的时候,若是用这个类实例化了一个对象,那么假如这个对象空间大小为 0 ,那我怎么才能证明它存在?所以说,空类的对象大小占一个字节,该字节的作用是占位,标识对象的存在。