C++_restart
Fast again
-
C++快速过一遍,理清C++基本知识框架。画出思维导图 — 10days.
-
STL回顾其基本特性,重点放在会用上边!!通过leetcode来熟练。 — 3days.
-
数据结构与算法,基本回顾,通过leetcode来训练。 — fast again!
-
重点先放在动手写代码上!!!先写起来!!!
有趣的问题
C++
Basic_usage
reference
引用就是对一个变量起别名,二者的地址相同。不占空间,底层实现机制 链接
引用的好处:
一是传值时 引用可以在同等级别解决问题,而指针是传递N级指针,解决N-1级的问题。
二是在子程序的返回值里,如果返回对象超过了该子函数的作用域会失效,运用引用可以将其值保存下来。
newdelete
C语言中的空间申请与释放
int *pi = (int*)malloc(sizeof(int)*10);
free(pi);
C++中 用new delete配对使用
- 对于单变量
int *pi = new int(100); //还可以初始化
delete pi;
- 数组
一维数组
int *p = new int[5]{0}; //分配5个int类型的空间 {} 花括号赋值 可写可不写
delete []p;
多维数组
int (*p)[3][4] = new int[2][3][4]{{{0}}}; //三维数组 初始化赋值的时候{0}到一维啊。
delete []p;
指针数组
char **p = new char*[4];
inline
主调函数执行时调用子程序块时,会进行保存现场等操作产生一定的时空开销。对于高频、短小的子程序块,可以使用inline内联函数来减小此开销。 内联实际上是将被调用的子程序块直接嵌入到main中相应的执行处。
inline 优点如上,缺点呢就是,每调用一次,就会在相应的执行位置进行函数拷贝,那么编译之后的文件体量会增大。这也体现了计算机处处时间和空间的矛盾性。另外,inline具体怎么做还要看编译器的做法了。
inline 和 #define还有相似之处呢,都是对其进行粘贴。
typecast
强制类型转换
C语言中
int a = (int)9.999; // a == 9;
C++
float f = static_cast<int>(10)/3; //隐式类型转换 stati_cast<type>(var);
reinterpret_cast<type>(expression); //重新解释变量类型
const_cast<type>(expression); //只能作用于指针和引用,去除const属性。
尤其注意:const_cast(expression) 将变量去除const之后,变量是不能更改的 (const类型的数据不可更改)。
const_cast is only safe if you are adding const to an originally non-const variable. Trying to remove the const status from an originally-const object, and then perform the write operation on it will result in undefined behavior.
原生数据是非 const 的, 可以去除其引用的 const 的属性, 若原生数据是 const 的,去除其引用的 const 属性, 执行任何写入操作都是未定义的。
代码列表中写了const_cast<> 的使用场景,感觉C++太严谨了,某个规则不完善,我就再另写一个规则来补充其用法。另外,一切对const变量的修改都是无意义的!即使能改,也是undefined behavior的,结果无意义。
namespace
命名空间,字面意思来理解就是:对变量、函数、类等的命名放在某一个{space}内起作用。用来解决大型软件开发过程中多人协作时命名冲突的问题。
#include<iostream>
using namespace std; //此处的std怎么理解? standard --> C++标准函数库
std:: 是个名称空间标示符,C++标准库中的函数或者对象都是在命名空间std中定义的,所以标准函数库中的函数或对象都要使用 std 来限定。
using namespace std 告诉编译器我们将要使用空间std中的函数或者对象。标记之后,标准库中的函数就可以直接用了,否则使用时每次都要写 std::cout<<"666"<<endl;
namespace
1. :: 称为域解析操作符,在C++中用来指明要使用的命名空间。
2. 使用 using namespace jmx;
同理,如果想用自己编写的函数库开发,只要将std更换为自己的就可以了。
对于多人协作的命名冲突:尽管相同的变量名,但是使用时分清楚是谁的命名空间就可以解决了。
string class
string 作为C++处理字符串的 类 ,经过重载运算符和添加成员函数,是的C++操作字符串更方便了。
- 重载了 + = < > += !=,操作更加便捷。
- 为string类添加了成员函数。在程序 string.cpp中有示例。
- 重点:str.c_str(); 返回的是
const char *
类型的 指向字符串的指针常量(临时存在)。简单解释
- 重点:str.c_str(); 返回的是
Class_oop
Encapsulation
封装就是将抽象得到的数据和行为相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成类。其中数据和函数都是类的成员,目的在于将对象的使用者和设计者分开,以提高软件的可维护性和可修改性。
数据:在程序中我自己定义的数据类型,如struct、int等。
行为:即对数据的操作,也就是函数块。
封装呢,就是将数据定义和行为封装在一起。对内数据开放,逻辑抽象,所有东西都是可见的;而对外只提供接口。 --> 实现将对象的设计者和使用者分开。
从C到C++对于封装的演进
C语言中的 struct 只能包含变量,而 C++ 中的 class 除了可以包含变量(数据),还可以包含函数(行为)。C语言中,将函数放在了数据定义的外面,然后把数据以指针的形式传给行为,数据和行为是分离的;
而在 C++ 中,我们将它放在了 class Student 内部,使它和成员变量聚集在一起,更像一个整体。通过类定义出来的变量呢就叫做“对象”! 类只是一个模板(Template),编译后不占用内存空间,所以在定义类时不能对成员变量进行初始化,因为没有地方存储数据。只有在创建对象以后才会给成员变量分配内存。
C++ Class
- 权限控制。 public、private、protected
- 在class中,将数据和行为放在一起定义
- 对内数据开放:不管数据是私有的还是公有的,在class内部的行为都可以使用。
- 对外只提供接口来使用。
- 使用流程:定义类,生成类对象,对象调用行为来实现需求。
类名其实也是一个namespace。 在函数
Constructor
构造器,在类创建的时候自动调用的(类似于函数的东西,可以用来对类对象的自动初始化。
比如String类,既可以string s;
也可以string s("xxxxxx");
就是构造器的作用。
基于封装的栈的练习,可以将栈的 void init()
该函数放在构造器中,使其在创建对象的时候自动将空间进行初始化。二是对于栈空间大小,可以使用构造器初始化传参来确定栈大小。
构造器特性
-
与类的名字相同,类似于函数但没有返回值,生成对象的时候自动调用进行初始化工作。
-
可以有参数 --> 有参数就会有重载、默认参数的行为。
重载即为 有参和无参构造两种。在构造时重载和默认参数二者不能同时用,否则会起冲突。
-
当我们不写构造器的时候,系统会默认生成一个空构造。
Destructor
析构:销毁对象空间。释放掉系统自动分配的栈空间和手动new的堆空间。
对象的销毁时期:一是栈对象结束生命周期 {} 时,二是堆对象被手动delete时。
析构器的特性
- 与类名字相同
~destructor()
无参数 - 对象销毁时自动调用,主要是来处理类中自己手动申请的对空间。
- 析构函数的作用,并不是删除对象,而在对象销毁前完成内存空间的清理,准备做好“善后”工作。
注意一点,析构和delete在C++中是在同级删除空间。
//类定义
Class Stack
{
Stack()
{
space = new char[111];
}
~Stack()
{
delete[]space;
}
private:
char *space;
};
//定义对象
Stack *s = new Stack;
delete s;
比如在上述程序段中,定义对象时自动调用构造和析构,在对象结束周期的时候自动对Stack内的space空间进行销毁。但是在定义对象时也是在堆空间上的,所以后续释放应该再手动添加。作用结果:先释放space的空间,再释放对象s的空间。 (类比C中的free顺序,都是一致的)
Copy contructor
拷贝构造,即由己存在的对象,通过拷贝构造器来创建新对象。其内容都一致。
拷贝构造发生时机:创建对象时依赖已有的对象来赋值; 在调用函数时传对象或者返回对象。
浅拷贝和深拷贝
浅拷贝:A对象对应于一段物理空间,B对象(同类)拷贝A对象来进行构造。如果B对象拷贝完之后没有生成其自己的物理空间,而是指向A对象的空间。此时呢,就是浅拷贝。 这样会导致在析构的时候,A对应的物理空间就会重析构。double free.
深拷贝:类比于浅拷贝,B在拷贝构造的时候,会自己申请内存空间,然后再将A的内容拷贝过来。深拷贝更加安全。
类构造的参数列表赋值法
函数声明之后,实现体之前, 开头 : x(i),
参数列表的初始化顺序,和变量的声明顺序有关,与初始化列表的顺序无关。
class Stu
{
public:
A(string na)
:len(strlen(name.c_str)),name(na) //参数列表格式 效率更高
{
}
private:
int len;
string name;
};
上述顺序是不对滴。
理解参数列表的变量声明顺序,在private中 先定义的len,而后是name。所以在使用的时候不能先用len。。fatal! 关键还是看你定义时变量的顺序。
private:
string name;
int len;
先定义name,后边len的时候会用到name。----> 注意参数列表中变量的定义顺序。
this pointer
this指针是指向当前对象的指针。
应用1. 成员函数的链式应用。
本质上还是理解this指针指向当前的对象,所有指向的内容都是在当前对象里有效。
class Stu
{
public:
Stu * add()
{
this->age++;
return this;
}
private:
int age;
};
//应用
Stu s;
s.add()->add()->add();
类对象的练习 P114
Class extension
class member storage
对于类的大小,通过sizeof
可以得知仅仅只是类中定义数据变量的大小。那么成员函数定义在哪里呢。
实际上,每个对象都会分配数据成员的空间,但是所有类对象共享一个成员函数。也就是说,数据成员是类对象独有的,而成员函数是大家共有的,都可以调用。这样节省了成员函数的存储空间。
但是新问题又来了,每个对象又是怎么来调用成员函数的呢。
就像主程序里边的函数调用,通过传参来使得函数为你服务。在不同的对象调用成员函数的时候,区分不同对象的标志是对象的this指针。对象调用成员函数的时候,会将其this指针作为隐含的参数传入,为之服务。
const
const修饰类分为三种。 const就是钢铁直男,只要我定义了,你不管怎样都不能修改数据的!
-
修饰数据变量
使用参数列表来进行数据初始化。
-
修饰成员函数
void func() const {}
const 置于函数名之后,实现体之前。要求在函数声明和定义处都要声明const。(唯一确定函数,防止重载
const修饰的函数,不会修改数据成员。只能访问const修饰的函数 -> 防止调用的函数修改数据
能访问所有类内数据成员,但是不可以修改哦。
-
修饰对象
const string s1;
const 修饰函数,要求函数不能修改数据
consti修饰对象,要求只能调用const函数,const函数来实现不改变数据。 一层一层管控。
static
static为静态的,C++中static实现了在一个类内,多个对象可以共享的一个数据成员。(类似于成员函数,为类所有,不属于具体的对象) 成员函数每个对象都可以传其this指针对其调用,static变量实现同类之间数据的共享,协调行为。
修饰数据成员
- 类内声明
static int a;
类外定义int classname::a = 1;
使用时既可以通过类访问classname::a
也可以通过对象访问classmember.a
- 静态数据成员属于类,在类外存储。不占用对象空间。
修饰成员函数
- 静态成员函数只能访问静态变量。来实现对静态变量的管理。非静态成员函数, 在调用时 this指针时被当作参数传进。 而静态成员函数属于类, 而不属于对象, 没有 this 指针。
static const
二者修饰效果叠加,既不可以改变数据,又能实现数据共享。
初始化数据 const static int a = 100;
修饰成员函数 const static int display()
只能访问const static 修饰的变量。
class_pointer
指向类数据成员的指针, 是类层面的指针, 而不是对象层面的指针。
首先定义一个Stu的类,数据包括 string name
;int age;
指向数据成员(public 数据)
string Stu::*ps = &Stu::name; // 定义指向数据变量的类指针
cout<<s1.*ps<<endl; //栈数据使用指针
cout<<s2->*ps<<endl; //堆数据
上一篇: python如何实现堆排序(代码示例)
下一篇: Python编程:pip安装第三方模块
推荐阅读