面向对象
前言面向过程的语法基本复习完了,能记多少全凭天意。然后就是c++面向对象的特性了,主要是从class类的角度。
正文OOP(Object Oriented Programming)即面向对象编程。本质上还是一种思想,通过编程中的事物抽象成对象编程。与此相关的名词还有OOD(面向对象设计)、OOA(面向对象分析)等。
学c++的时候就知道它兼容c语言特性,所以外界的声音就是c++不单单是面向对象的语言。像java、c#可能就是纯面向对象语言的代表了。
不过毕竟是一种思想抽象,所以也不能百分之百说就是面向对象好,面向对象不好。还是要取决于使用场景。
面向过程12345678910111213141516171819202122232425262728293031323334#include<iostream>struct NPC{ int hp; int mp; char *name;};struct MONSTER{ int hp; int mp; char *name; int damage; unsigned price; ...
【编译器角度】assert
前言assert, 翻译可能是断言,本质是个宏定义,不是函数在C++中要使用assert需要将头文件<cassert>引入,虽然cassert本质上引用的是<assert.h>
它的作用在其他语言中是一样的,就是说如果他里面的条件返回错误,代码会终止运行并且将源文件以及错误的代码和行号都输出出来。
正文123456789#include<iostream>#include<cassert>int main(){ assert(false); return 0;}
弹出的类似这样。
像一些特殊场景我们想要输入的时候不为0,以便参与下一次运算。当然debug模式和release模式情况可能稍有不同,因为release模式下会自动屏蔽掉assert的功能。但是通过assert在debug下直接弹出一个警告框效果也不错。
123456789101112#include<iostream>#include<cassert>int main(){ int x; std::cout < ...
【编译器角度】debug
前言对于编写好的程序,或多或少可能存在bug和错误,简单语法上的问题编译器能够直接给出提示,而对于逻辑上出现的错误,编译器是不能直接发现的。所以就有了debug调试的过程。
正文为了方便调试,我们希望在编程风格上
模块化
使用有意义的变量名和函数名
控制缩进,首尾呼应
良好的注释习惯
1234567inculde 《stido.h》int mian(){ print("hello world"); retrunt 0;}
像这种。。字面上的错误都算是语法问题,编译器一下子就能找到问题,人眼也能看到。。这种其实就不算什么bug
而逻辑错误:
12345int ch[][3]={ {1,2,3}, {4,5,7}, {8,9,10},}
这一种由程序生成后,看到其实不是按照预期结果从1-9的,在其中一个地方发生了什么导致跳过了,这种问题他不影响运行,但是与我们预期结果不符合,就可以说是一种逻辑错误。
模块化123int add( ...
【编译器角度】预定义宏
前言在.c和.cpp混用的时候,注意到了它们在处理函数的时候有所不同然后通过#ifndef _cplusplus去规避。那么这个_cplusplus就是c++的预定义宏
正文func__func__编译器支持iso c99和iso c++11指定的预定义标识符它的作用就是代表函数的名称
123456789101112#include<iostream>void hhh(){ std::cout << __func__ << "\n";}int main(){ std::cout << __func__ << "\n"; hhh(); return 0;}
这种替换成函数名的操作,适用于debug的时候给函数做标记,看看是卡在哪个函数了。
标准预定义宏
宏
说明
__DATE__
源文件的编译器日期
__TIME__
当前转换单元的转换时间
__FILE__
源文件的名称
__LINE__
当前的行号
__cpl ...
【编译器角度】预处理指令逻辑
前言预处理:
12345#pragma once#define xxx#ifdef xxx#else #endif
诸如此类命令。
正文ifdef一般都出现在头文件。主要是为了防止重复。
123#ifdef _HH_#endif
像这样在头文件定义后,除非源文件有#define _HH_不然里面的内容是无效的。
12345#ifdef _HH_ ...#else ...#endif
然后if与else配套传统,如果不存在,就还有一种结果。
ifndef最早可能用过这种,#ifndef是"if not defined"的简写。
在特殊场合多次包含中,可能会这么用
123456#ifndef _HH_#define _HH_...#else#endif
该段代码意思是:如果_HH_没有被定义,则重定义_HH_,即执行语句2、语句3;如果_HH_已经被定义,则直接跳过语句2、语句3,直接执行语句4…
当然这种处理方式就可以不用老实呆在头文件里,你可以放到源文件去处理一些事情
12345#ifdef UNICODEwchar_t a;#elsecha ...
【编译器角度】namespace
前言常用。
1using namespace std;
std就是一个命名空间。
当然最终感觉还是为了便于管理。
123namespace hack{ int ....}
把需要用到的函数和变量或者结构体之类的塞到一个命名空间中,这样下次调用起来就很方便:hack::...
正文123456789101112#include<iostream>namespace hack{ int value;}using namespace hack;int main(){ value = 200; return 0;}
这里因为解锁了hack空间,所以里面的变量可以直接调用,但是这样不好的事如果别处有同样定义的变量还是会冲突的。这也是为什么大部分人其实一开始不会直接using namespace std;因为std里面有很多的变量啊之类的,就怕你词穷冲突了。解决的办法就是用到啥using一下:using std::cout;。良好的代码习惯固然重要,但是后期团队协作还是要看整体风格
全局命名空间就是说具有链接或 ...
【编译器角度】define
前言#define 通常用作定义常量如#define Pi 3.14
甚至替换类型之类的操作#define 整型 int以至于当时无聊的时候还真有人用define去写中文编程语言
12345678#include<iostream>#define 整数 int#define 输出 std::cout int main(){ 整数 a = 100; 输出 << a;}
也是能跑的,但是玩归玩
宏定义的名称也与变量名称规则一致,不允许数字开头
正文正经情况实际上嘛,常量除了define还有个const#define定义在头上可能是因为更直观吧,像windows api里面就有一堆#define
12#define SCREEN_WIDTH 1980const int screen_width{1980};
c时期define用的多,c++可能更多的用const,但是因为先学c在学c++,所以习惯是潜移默化的。const定义的好处就是类型检测,限定类型肯定更加严谨。而#define则相对无脑替换,你可以 ...
【编译器角度】ODR
引言全称为One Definition Rule,可以说是单定义原则或者规则是为c++的语法规则,这个规则指出变量只能有一次定义。
正文编译过程回顾编译的四大过程。预处理,编译,汇编,链接。预处理 就是替换所有的宏定义,展开include的头文件,去掉注释和一些无关的。[由.c/.cpp 到 .i]编译 就是将高级语言的代码转换成机器语言的过程,主要进行语法之类的分析,优化成汇编。 [由.i 到 .s]汇编 就是将源文件翻译成二进制,Windows会转换成.obj文件,类unix的为.o文件。 [.o文件时机器码所以纯文本无法正常显示]链接 就是将.o文件和库文件绑定到一起,最终形成所谓的可执行程序。
这里的编译器用的visual studio了。抽象上,.c/.cpp + include<.h> 合并后称为转换单元然后转换成.obj之类的,因为是windows平台。最后链接成可执行程序.exe当一切顺利时,自然不会有问题。语法上的编译器在编译器之前就能给出错误和警告。
1234567891011#include<iostream>v ...
递归函数
前言递归主要是应对与数学场景吧。简单来说就是函数 直接或间接的调用了自己,那么这个函数就可以说时递归函数内联函数不能作为递归函数
123456void hhh(){ if(...) hhh(); else ...;}
正文接触最早的递归函数应该是阶乘了。n! = 1 x 2 x 3 x 4 x…(n-1) x n
按照函数的操作,就是传递一个参数,每次乘以这个参数-1,当参数==1的时候就返回参数就行了。
1234int func(int x){ if (x == 1) return x; return x * func(x - 1);}
假设x=10
10 x func(9)9 x func(8)8 x func(7)7 x func(6)6 x func(5)…2 x 1 // x==1,return x;所以不进函数了。
递归函数用的时候一定要能结束。。。不然堆栈溢出程序也没法跑
当然你说这种操作用for循环 ...
函数调用约定
前言函数调用和被调用之间肯定要规定,如何传递参数,如何恢复栈平衡的问题。
正文程序运行最开始,main函数肯定是第一个入栈的,其他的函数被调用时相继入栈。
_cdecl12345678910#include<iostream>int add(int a, int b);int main(){ add(1, 2); return 0;}
编译自然报错,但是我们看报错信息
之前说到过C语言处理函数在.obj文件下都是加一个_而c++因为有重载的功能所以还多了参数,除此之外就是_cdecl
_cdecl的参数入栈 从右到左
堆栈平衡:谁调用谁平衡
所以这种方式能够支持不定量参数。 不定量详情看function1吧,好像写在那里了。
虽然不定量,但是毕竟是从右边开始传递,所以不会出现丢三落四的情况。
_stdcall因为一般都是_cdecl 所以这玩意比较少见,但是反正能指定。
123456789101112#include<iostream>int _stdcall add(int a, int b){ return ...