♥C++宏观技巧笔记

宏观问题:

  • 从最直观的的做法开始,一步一步优化,并不是每个人都能第一时间想到最优解,要让代码在第一时间内正确的运行才是首要的,然后在不影响代码的外观行为下改进代码。
  • 对于面向对象编程的程序员来说,自顶而下的思考方法才能发挥面向对象的真正威力,通过抽象,我们可以在编码的一开始就已经构建好整个框架,这样有利于我们工作进度的把握和测试,更加能够锻炼程序员的抽象能力,这在接口设计中非常重要,而接口的设计就充分体现了面向对象程序员的价值。
  • “这样用户会在创建字符串前先调用这个函数,然后再调用error()来终止程序。”
      我们无法代替用户做出准确的选择,因此折中的方案就是我们提供检查的方法,然后由用户来显式的调用,这是一种信任,但很多时候这种信任都会被打破,但至少责任是在用户那里。
  • 白板代码,可以把random,swap,len等写成一个函数不写具体实现,而注意完成核心代码即可
  • int sum,avg=0;      //这样不能初始化两个为0,必须这样:int sum=0,avg=0;  //这是最简单的方法
  • O(1)时间不是只能一次,而是常数时间
  • 当一遍遍地修改题目的时候,面试官实在上是在问扩展性的问题
  • 带一支铅笔和橡皮,随时加上N记号(检查NULL),画草图
  • 尽可能画出草图跟面试官核实思想【这个时间一般是不计算在内呢】
  • hash就是根据差集制作的表,跟异或运算有天生的吻合度...经常一起使用

解决复杂问题:


问题类型 解决手段 思想 描述
数据结构 画图 图的信息量和整体感  
  • 复杂过程或算法
  • 看不出规律
  • 例子
  • 例子特征表
机器学习特征分析  
  • 有递增规律
  • 分阶段
  • 分治
小中见大的分形思想 并通过递归或者动态规划映射到大的问题

鲁棒——防御性编程(带检查的编程):

指针一用到就检查NULL
参数考虑0,1和负数,值相等
递归考虑终止条件

数据传递和释放:

  • 释放内存的时候,需要先释放掉每个元素的内存,再释放点整个数组的内存,这个顺序不能颠倒,否则就会造成原本被释放掉的内存第二次被释放 
  • 为了存入多个返回值,把需要返回的东西作为引用方法传入。
  • 【一般只把返回值用于返回bool的成功/失败信息】
  • 非void返回的,如果最后一句不是return,那么最后一句之后就是隐含退出点....必须修改并给出一句return
  • 在形参列表中用const修饰引用,其实只是让阅读函数的人了解我们并不想改变这个引用而只是想用它的值
  • 引用传址机制最好只用于类对象,其他int类型之类的最好别用


安全与测试:

测试用例:
  • 功能测试  可能只处理了最简单的情况
  • 边界测试
  • 负面测试  可能的错误输入

安全的显示:
比如display (表达式A)
可能表达式A根本不能正确计算,所以应该
if(A)
      display(A);

发生错误时的返回:
  优点 缺点  
非0返回值 跟系统API一致 不能方便地使用计算结果作为参数 系统API
全局变量 把是否错误放在一边能直接把结果返回 可能忘记去查看 atoi
异常(try) 清晰明了,能处理不同异常 对性能有影响  

采取防御式编程:
如果不...那么....
如果哪怕因为别的程序的错误,都要在自己的程序中使用兼容性处理以避免发生错误的输出,即一定要兼容性处理,对于错误的输入可以输出warning

C++设计模式:

  • 单例模式:通过资源锁等功能,同一时刻只允许存在一个实例
        实现:定义一个自己类型的指针,初始化的时候就检查是否已经设置了这样的类(因为是static的对象,所有类的对象都共用这个指针)
        同时派生出一个静态的垃圾工人子类,垃圾工人具有一个析构函数会清理这个指针,置为NULL,从而在程序结束后自动把指针所指的资源释放掉
class CSingleton  
{  
//其他成员  
public:  
static CSingleton* GetInstance()
{
      if(m_pInstance == NULL)
            {m_pInstance = new CSingleton();}
      return m_pInstance;  
};  
private:  
    CSingleton(){};  
    static CSingleton * m_pInstance;  
class CGarbo //它的唯一工作就是在析构函数中删除CSingleton的实例  
{  
        public:  
            ~CGarbo()  
            {  
                if( CSingleton::m_pInstance )  
                  delete CSingleton::m_pInstance;  
           }  
 }  
        Static CGabor Garbo; //定义一个静态成员,程序结束时,系统会自动调用它的析构函数  
};  


  • 简单工厂:针对不一样的参数实例化不一样的对象
  • 工厂方法:用多个函数包含各种简单工厂的实际方法,各种简单工厂的父类,每要实现一种特定的简单工厂的时候,就使用里面不一样的方法,实例化一个这样的工厂
  • 抽象工厂:用一个函数包含各种简单工厂的实际方法,实际上不存在,但是当实例化的时候用一套函数就能表现出对应工厂的行为,抽象工厂用于把多个具有相同模式的工厂抽象化
  • 适配器:用新的调用方法调用旧的代码。用一个类实现一个方法,这个类接受3.5版本的调用,并在这个方法里,调用2.7版本的对应的函数。
  • 装饰器:通过重载父类扩充原有类的功能。你给我一个需要装饰的对象,装饰器就会重载它里面的函数,以增加新的功能,之所以它能够重载里面的函数,是因为装饰器实现了需要装饰的对象的父类
跟适配器相似,适配器目的在于让两种不同的对象能相互融入,装饰器,在于不断重载以增添原始函数的功能
相当于在适配器的适配的函数部分,运行了原始类的函数,并在运行之前或者之后增加了新的功能,重点在于用了装饰器就能把旧的类增加新的功能,因为你要把父类传进去
  • 代理模式:给旧的类做一次把关。同一个类而去调用另一个类的方法,不对这个方法进行直接操作
  • 观察者模式:他保存一个观察者的列表,并保存一个指示是否被更改的bool变量。
                        【 两种更新方法:1.setChanged()函数里调用notifyObservers方法 2. 提供notifyObservers函数(通知函数),一调用这个函数,如果hasChange等于一,就对每个观察者调用其update方法 

递归:

  • 递归实际开销很大,当数目可能很多的时候,用循环来代替递归以提高效率。
  • 递归的情况,注意可以用两个函数recursive和recursive_core,其中 recursive负责完成一些外围检查问题,还有一些收尾工作(只需要做一次的工作),然后调用 recursive_core 进行真正递归模块
  • 一个算法应该只有一个循环,如果想要提高效率,可以考虑递归,但是递归的可读性并不直观。

VS的问题:

如果没办法新建项目,可能是解决方案正在调试

C++命名规范:

/结构
类的名称都要以大写字母“C”开头,例如:"CAnalyzer", "CFastVector"
结构/宏:全部由大写字母组成,单词间使用下划线界定,例如:"SERVICE_STATUS", "DRIVER_INFO"

函数:
大写开头,动词+名词
私有成员__

变量:
作用域前缀【一般可省,全局变量加s_】+类型前缀【n umber/c har/b ool】+单词(开头大写)


几步曲:

1.弄清楚问题
2.无效输入/特殊测试用例
3.提出简单的思路
4.不满意后进行问题分析:画图,举例做表,分治
5.复杂度分析:
6.写代码,注意规范


降低时间复杂度:

算法优化:动态规划,分治
空间换时间:hash,储存中间结果


需要的能力:

沟通
知识迁移【用其他领域的模型解决方案】
抽象建模【联系到已有数据结构】
不同角度发散思维【表现出探索新思路的激情】

代码重构:

代码广度优先

我写代码的时候,比较喜欢先去定义整体的框架,就是写很多空实现,来把整体的业务流程穿起来。

避免过长参数

如果一个方法的参数长度超过4个,就需要警惕了。一般会用一个struct或者一个class来承载数据

为每个方法找到合适的类归属,数据和行为尽量要在一起

面向接口编程

面向接口编程是很多年来大家形成的共识和最佳实践。最早的理论是便于实现的替换,但现在更显而易见的好处是避免public方法的膨胀。一个对外publish的接口,一定有明确的职责。要判断每一个public方法是否应该属于同一个interface,是很容易的。


发布了51 篇原创文章 · 获赞 62 · 访问量 12万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览