第一天

1.sizeof 一个对象,求得的数据部分的尺寸,而不求代码(也就是函数)的尺寸。

一个对象数据和函数是分开存储的,有数据段,代码段等。涉及到C++对象模型的知识。

这样的设计即多份数据共用一份代码(函数)只适用于单cpu执行的情况。一个CPU一次只能执行一条指令,所以每个对象都有自己的函数代码

所生成的指令是没用的,因为cpu每次只能执行一条指令,也就是只能访问一个对象的指令。但是这样的对象模型如果是并行计算则会出现问题。

例如对象A在CPU1里面跑,对象B在CPU2里面跑,那么编译器只生成一份函数指令则会出现问题,因为两个对象要同时执行这个指令,即两个CPU

的程序指针同时指向该函数指令所在的内存。设计到并行机的知识。



第二天

1.一个类声明和实现分别写到头文件和源文件中,这样成员函数是非inline的。声明时用inline修饰就可以使该成员函数成为inline的。

如果一个类声明和实现写在一起,也就是在类里面直接写实现,这样成员函数是inline的。


2.C语言函数是禁止嵌套定义,必须嵌套调用。



第三天

1.静态先于main函数的执行而存在。


2.所有计算机理论在杨力祥看来不外乎两个词:序和可识别。


3.数据结构的那些设计就是由于硬件的设计所导致的,物质基础决定上层建筑。


4.鉴赏力:例如拿一本书翻几页,知道它要实现个什么东西,然后合上书,自己去思考,

去设计。自己设计完之后,再打开书和别人的去对比,这样就能发现大师设计的想法是多么优秀。

可以看到自己跟大师的差距。学东西最怕的就是学了十几二十年看不到自己和别人差在哪里。

一个人有没有水平就体现在鉴赏力上面,能不能看出它好在哪里。


5.用体系性思维去思考问题,把一个东西放到体系里面去思考。


6.C语言调用函数时将局部变量压栈出栈,那么如果将所有的局部变量都定义为静态可以吗?

不难发现静态全局的访问速度更快,而压栈还需要不断的进栈出栈。栈是动态使用内存,

静态变量是静态使用内存,也就是编译器已经算好它的大小及数量等。如果将局部变量定义为静态,

所有的情况都可以解决,但是有一个问题解决不了,就是自己调用自己,或者说递归。

说明C语言在设计时,递归在整个体系结构中占了很重要的地位。


7.指针是地址吗?如果指针是地址为什么还要起这么个名字,直接叫地址不就好了。

在计算机里指针主要分为两大类,一个是指令指针,也就是程序指针,另一个就是数据指针,

数据指针往往用寄存器来代替。指针就是一个指向数据的地址吗?在计算机中每个字节就是一个地址,

但是一个数据不一定只占有一个地址。指针指向的是一个数据的首地址,且指针是有类型的。当拿到一个指针的时候,

也就是拿到了一个数据的首地址,拿到首地址后应该向下数几个字节来拼成这个数据,就由指针的类型确定,

拼成这个数据之后,编译器如何解析这个数据,例如4个字节,该解析为int还是float,这个也由指针的类型来确定。


8.指针的三个优点。第一,以小搏大。即就地解决,山不过来,我过去的思想。用于函数传参时传址。

传参时不把所有数据都带过去,复制一遍,而是将所有数据的内存地址传过去。

第二,切换。指令的切换,函数指针,也就是指令指针,例如动态链接库,用指针在动态链接库里来回切换函数,

调用里面的各种函数。数据的切换,数组不是一个类型,只是一个组数据的打包。数据的切换例如数组中的行指针列指针的应用。

第三,就是挂接,最常用的挂接就是链表。用指针把一组数据连接起来。


9.指令指针寄存器IP(X86型CPU)相当于ARM型CPU中的程序计数器PC。


10.IP在main函数指令执行中遇到调用函数,也就是跳转指令,跳转到另一个函数的指令位置,然后开始

执行,执行完该函数的指令之后,IP又跳回到原来main函数指令的位置。CPU中有一大堆寄存器用来计算等。

当调用函数IP还没跳转时,CPU中的寄存器存储的是现在正在计算的那些数据,也就是跳转前的状态。但是

IP跳转之后,CPU开始做新的是事情,也就是CPU中的寄存器开始处理新的计算,而跳转前正在处理数据寄存器

的值就会丢失。当该函数调用完之后,IP返回原来的位置,CPU的寄存器也应该会返回到跳转之前的状态,继续

干原来的事情,但是调用函数的时候,CPU的寄存器已经处于调用函数时的状态了。该怎么解决这个问题?

将寄存器的数据进行压栈和出栈,即保护现场和恢复现场。 内存的栈区,其实就是纯粹的数据。

返回值为基本类型的直接走寄存器,而返回值为自定义类型的往栈里走。


11.调用一个函数栈里面存放的东西依次是(由编译器决定):CPU原来的状态(现场保护),返回值,形参,然后是一些局部变量的数据。

例如在调用函数时,执行char a = 'a';时,实际就是将'a'这个值进行压栈,将这个数据放到栈的内存里面来进行使用。

压栈出栈其实就是一个栈指针的来回走动。调用函数传参,其实就是向栈里面的形参的那块内存直接写一个值,


12.程序是如何运行的?点击一个exe,实在操作系统上运行一个程序,操作系统将exe的一些有效文件从磁盘装载到内存中,

对于C来说装载两类数据,一类是代码,另一类是静态数据。静态数据又分全局变量和静态局部变量。

然后指令指针IP指向main函数入口点的对应位置。然后开始执行。可以发现静态数据是先于main函数的执行而存在,

这也是静态数据的一个巨大的作用,一般而言函数都是在main函数执行之后才能执行,

但是一个全局对象的构造函数是先于main函数的执行而执行的。IP指向main函数入口之后,程序开始执行,也就是IP开始自动往下走,

IP走的方式有两种(有硬件决定),一种是顺序执行(沿着指令的顺序自动往下走,CPU的功能,不需要人为干预),

另一种是跳转(由程序员控制,例如循环(向上跳)和条件判断执行(向下跳))。


13.静态数据在被加载到内存而不是在硬盘上的证明:

int a[N];

int main(){}

build如上程序,将N设置为10000,而1,比较两个程序的可执行文件exe的大小,发现两个文件一样大,也就是说这个静态数据

并不在硬盘上,而是操作系统加载时,开辟到内存的。



第四天

1.c/c++中有一个先声明后使用的原则。

声明(声明变量和函数)是给编译器看的。定义变量时才会开辟内存,声明不会开辟,声明是个编译器看的,如果声明了一个变量,

且后面没有使用,则编译器会将其优化,视为不存在,不会开辟内存。


2.写一个函数的顺序是先声明,再定义,最后调用。但是声明和定义可以合起来写。例如下面的写法:

void Swap(int, int);

void main() {

    Swap(a ,b);

}

void Swap(int x, int y)  {

    int temp = x;

    ...

}

首先声明函数Swap是为了给编译器看,知道了其返回值类型和参数类型几个数,只是为了计算其在

栈中占用空间,方便栈指针跳转等(不得而知),所以不用写参数名字,有了其类型就够了。

而定义的时候,是必须要知道参数的名字的,第一因为函数中要用到这个参数。但是问题来了,如果

不写参数名字,调用函数时形参在从左到右一个一个的进栈时,由此每个参数在栈中的内存地址

我都知道了,函数中的使用不就可以直接访问那个地址了吗?这也就导致了第二个原因,函数参数

进栈的顺序是不一定的,所以必须为其加上名字,后面才能准确拿到参数。


3.编译是以*.c或*.cpp文件为单位编译的。一个程序里面有几个cpp编译几次,一个cpp只编译一次。

#include "a.h"是将该文件a.h的内容嵌入到当前位置。例如下面某个文件的代码。

#include "Circle.h"

int main() {

  Circle circle(1, 2);

}

由c/c++先声明后使用的原则,只要在该cpp文件中声明了Circle这个类,就可以使用。所以写上了

#include "Circle.h"这句话,将Circle的声明代码嵌入到当前这个文件。


4.地址重定位:例如在main.c中调用了a.h中的函数,a.h中的函数只声明而不定义,所以

在main.obj中,也就是在main.c编译之后的机器码中,IP跳转到该函数的地址是不知道的,因为该函数的

.obj还没有链接到一起,所以这些调用函数地方指针都空出来了,当所有obj都拼到一起之后,编译器算出

该函数地址的值赋值给原来指针的地方,也就是地址重定位。


5.一个.cpp文件编译之后一般来说生成一个.obj文件(目标文件,机器码,可执行,但不是立即可执行)。

然后将多个编译生成的.obj文件以及标准库等其他的.obj文件拼在一起后,然后进行地址重定位,将

其之间的关系全部联系起来。最后加入一些头等打包成exe。


6.所谓lib库等,就是将一大堆的.obj文件合起来,然后加入一些引导头之类的。


7.上述讲的这些链接,都是静态链接,链接之后还是在硬盘中,执行的时候加载到内存中。

例如printf这个函数的代码,在运行之前已经存在于exe,这就是静态链接。

相对的就是动态链接dll。


8.链接printf的时候,不是讲"stdio.h"这个库连接进去,而是将printf链接进去,以最简化原则,

需要谁连接谁。


9.静态链接中,例如连续调用多次printf函数,实际上就链接了一次,只产生一份代码,只是传的参数不同。

但是站在操作系统的角度来看,系统中有多个程序在执行,每个程序都调用了printf,也就是每个程序都

链接了一次printf。站在程序的角度看,自己已经很节约了只链接了一次,但是站在操作系统的角度看,

链接了多次,产生了浪费,其实只要搞出一份代码就可以,自己的程序访问别人程序的代码,

但是这样就导致了跨进程的问题,十分不安全。而动态链接库就解决了这个问题。动态链接库没有将代码

放到某个程序中,而是放到操作系统中,当某个程序使用其的时候,应用loadLibrary这个技术,加载该库。

如果要用printf函数,就从dll中找到该函数的地址。原来是静态的算好printf函数的地址,现在变成了动态

获取printf函数的地址。


10.作用域,生存周期。

例如static局部变量,全局的生存周期,局部的作用域。


 


第五天

1.讨论:对象定义时,为什么要对象自动调用构造函数,而不是外部调用?

思想猜测:面向对象是尽量将一个类进行物化,它自己就是一个物,产生之后就是一个完整的物体,而不需要别人调用构造函数

来进行物体的组装,初始化等。面向对象,一切皆对象,里面是物与物之间的交互,模拟人类社会,如果一个物产生之后还需要

别的物来调用它的函数将它初始化,这是非常诡异的。所以一个对象应该在它被使用之前就初始化好了,它不应该由对象的使用

者来初始化。


2.为什么要设计构造函数,析构函数?

a.更好的体现“物化”思想(自动性)。

b.更好的封装性,避免从外部初始化。很显然外部初始化对象的成员变量破坏了对象的封装性。

c.更有效地使类架构设计与程序架构设计分开。

d.促进更清晰地面向接口编程(所谓的接口都是public的函数),更有效地复用。


3.例如一个类里面维护了一个数组,然后写了一大堆public函数,来操作这个数组。既然可以直接操作

数组,为什么要将其封装提供接口呢?好处就是提供接口,面向接口编程,松耦合,更容易复用。并且

将其物化后,提供接口跟别人交互,更像是一个人用语言去跟别人交流。并且将数据封装,更好的保护

了数据,不将其暴露出来。


4.new和delete是操作符,不是函数。malloc函数只是分配出一块内存,而不管分配出什么类型的。需要什么类型

自己去强转。而new操作符不但分配了内存,并且赋予了其类型,引发其构造函数的调用。



第六天

1.程序一开始写的时候最难,无法下手。当程序写了一大堆时,思路打开后,却不难了。


2在当解决问题时,会遇到问题难度大于自己能力的情况。此时人们采取的方法有两种,一种是提升自己的能力,

快速学习相关知识等。但是能力并不是短时间能够提升的。能力的提升是金字塔状的,一层一层铺上去的。

真正的大师站在金字塔的顶端的时候,底下是铺了一大层的。第二种就是将问题的难度往下缩。缩小的办法

之一就是去掉问题包装层,留下问题最主要的东西,是它的最小完备集。


3.看传记,最好是看大师本人写的,不要去看其他阿猫阿狗写的,他们怎么可能理解大师所想的。


4.书读了十几年,是时候去动脑思考问题了。遇到一个问题有没有能力去解决,就看你有没有能力去将问题解剖,

然后用已学过的知识去分析。世界的本原是相通的,所以所有的知识都是相通的,那么就可以用自己学过的知识

去解决其他陌生领域的问题。通过这个思想,那么你是否可以通过十五天的时间去成为某个其他领域的专家?

因为知识是相同的,思想是相通的,世界的本原是相通的。


5.函数重载时,为什么函数参数和参数个数是可识别的,但是函数的返回值是不可识别的?

因为调用这个函数时,调用语法是没有返回值信息的,由此导致调用时不可识别。

从定义函数的表面上可以通过返回值识别,eg:

int Fun();

float Fun();

从定义语法上,以上两个函数可以识别。但是在调用语法上面是不可识别的。eg:

void main() {

  Fun();

}

从语法上如何识别调用的是哪个Fun函数呢?不能识别。

如果语规定说调用的时候,前面必须写返回值类型,那么这可以识别。

从这个问题可以分析到,一个语法中,哪个行为可以这样做,哪个行为不能这样做,

是在整个语法体系中相互制约的,所以可以从整个语法体系去分析某个行为为什么

是这样的,一定是有原因的,是有其他行为与其相互作用,相互制约,相生相克。


6.void在当返回值类型时,表示不返回,并不是返回无类型,不返回任何值,不去建栈。


7.构造函数可以显示调用,匿名对象。


8.构造函数没有返回值,是编译器造出来自动调用用来初始化对象的,它不需要返回值,返回给谁呢?



第七天

1.析构函数不能重载,不能显示调用。


2.C++的发明者喜欢用一个词代表多个意思。例如static有多种用法,const也有好多种用法。


3.静态局部对象。静态变量的产生早于main函数的调用,但是构造函数的调用不会早于main函数。

可能是在main函数一开始的时候,也可能第一次使用的时候,例如静态局部。

分析:代码如下,

A a;  // 构造函数输出a。

int main() {

  cout << "b" << endl;

}

输出为 a, b

先输出a,后输出b,那么是不是就是首先调用A的构造函数,然后调用main函数?

如果是这样,那么是不是和C语言是一个悖论,C规定函数必须嵌套使用,但是不能

嵌套定义。但在这种情况下A的构造函数不是在main函数中调用使用!并且如果A的构造

函数可以在main之前调用,那么是不是可以把所有的事都放到构造函数中去做,main函数

其实就没什么用了?

事实证明这种想法是错误的,通过查看编译生成的机器码可以看到,全局A的构造函数

是在刚进入main函数的时候,就调用的A的构造函数。

由此也可以看出C++是一个不纯粹的面向对象的语言,因为它要进入main函数,在里面先做这个,

后做那个,很面向过程的一种风格。如果更纯粹点的话,就把main函数去掉,也就是把main函数做成一个

类,由该类来带领队伍去做各种事,也就是将其对象化,而不是面向过程的,在main这个函数中进行动作。

在现实中的面向对象都是纯粹的面向对象,因为不论做什么事,都是有人,有角色去带领,去做。


4.面向过程,把一个问题的解决方案步骤后,过程化,先做这个,然后做那个,最后再做什么什么。

而面向对象是将问题进行角色划分,进行物化,进行专业化分工。


5.问题分析:代码如下,

int a = 5;

cout << &5 << endl;

5是常量吗,为什么不能对其取地址?

5是一个常量,不能对其进行赋值。5是int a = 5;这条指令中的立即数,这个5是这条指令中的一部分,

是不能将其取出来的。指针虽然有指令指针,但是指令指针是指向某条指令的,

而不能将某条指令拆分开来,取到其中的某一部分。

在C++中常数即常量, 如:int a = 5;

常量5,不在数据中,而在指令中,没有指针,不能赋值。


6.const常量,具有常量性质的数据(编译器“把门”)。常量必须初始化。

const int a = 10;

a = 12;  // 错误,这个赋值错误是因为编译器把门,不让它通过,而不是因为其他的原因。


7.const推测:

const int a = 10;

cout << a << endl;

int b = a;

推测一:如果代码中没有对常量a取地址的行为,那么,编译器就直接将其视为宏的作用。也就是

在编译初期,直接将a替换成10。

const int a = 10;

cout << &a << endl;

推测二:如果代码中有对常量a进行取地址的行为,那么const的作用之一就是将a进行内存分配,

将其变成一个普通局部变量。const的作用之二就是提示编译器进行把门,不要让程序员对其

进行赋值。


8.const    成员函数:约束成员函数的只读性。const很巧妙地解决了野指针的问题。


9.引用,是一个变量的别名,引用本身并不是没有存储空间的。引用不会为值去开辟一个

地址空间,但是其本身要占用空间。引用确实占用了栈空间,也确实是存储了目标变量的

地址。


10.初始化非const引用必须用左值,const引用则不必。

eg:

int& a = 5;  // 错误

const int& a = 5;  // 正确

引用在内部存放的是一个变量的地址,它是变量的别名。对于不可寻址的值,编译器为了实现引用,

必须生成一个临时对象,引用必须指向该对象,但是用户不能访问它.

一种不准确的解释为:编译器产生的临时产生的中间变量都是const的,即其为常量,

所以必须用const的引用

eg:

const int a;

const int& b = a;  // 因为a是const的,所以其引用也必须是const。

这种解释是有悖论的,如果其为const,那么其应该在指令区,是不能做左值的。

但在《Thinking in C++》中说到编译器产生的临时的中间变量是可以做左值的。

例如const int& a = 5;编译器会为5这个常量产生一个临时的中间变量,且这个临时

中间变量是可以作为左值得。


比较正确的解释为:临时中间变量是程序员是不能对其进行操作的,而且其在使用完之后

就会释放,所以我们修改一个其是没有意义的。所以编译器就为其加入了不能作为非const

引用的这个语义限制,防止用户错误使用。也就是说临时中间变量有const的性质,但不是const

变量,所以编译器加入了这个语义限制。



第八天

1.C++体系性的思考特别多。体系性思考特别重要。



第九天

1.世界上最难做好的就是最简单的道理。人有一个毛病,越是聪明的人越有的一个毛病,

追求复杂很难理解的道理。


2.在拷贝构造函数中为什么可以访问引用对象的私有变量?

一种解释:因为C++的封装层次达到类的层次,而不是对象。所以对于C++来说访问权限是针对“类”

来说的。也就是说private的访问权限是其他类不能访问的,而不是这个类的不同对象之间不能访问。


3.成员函数和非成员函数之间最大的区别就是,成员函数的参数里面有一个你看不见的参数,

这个参数传的是this指针。


4.this指向当前对象的指针。



第十天

1.学习,学会规则只能达到解决温饱的问题,真正要达到衣食无忧,靠的是悟性。

悟性对一个事物的思考,把握。学过一个东西,然后能掌握它,抓住它的主要矛盾,这是只是一个基本的本事,

真正的本事是没有学过一个东西,能够抓住它的主要矛盾。


2.其实很多高考新课标的修改就是一种思路。新课标的题目被称为很开放,开放并不是乱七八糟的无厘头,而是

一种思想的开放。通过已有已学过的知识,去分析一个问题,然后设计解决方案。这就是新课标的设计类题目。

这也就是其被称为开放的原因。学习,去思考,去推理,达到设计的境界。


3.对事物要有一种“敏”,也就是对事物有一种敏感,快速感知其思路,其设计的伟大之处,以及要达到的目的。


4.生物的继承进化,基因既有自己的基因,又有继承前人的基因(加机制),但是并没有完全加入了前人的基因

(减机制),一些前人的基因可能没有继承。但是在C++中,虽然有priave,但是还是完全继承了前人的基因。

也就是C++只有加机制,没有减机制。

eg:

class Base {

private:

  int a;

  int b;

};


class Derived : public Base {

private:

  int c;

};


int main() {

  cout << sizeof(Base) << endl;  // 输出8

  cout << sizeof(Derived) << endl;  // 输出12


  return 0;

}

生物不断地进化,可能进化出新的物种。如果C++就像生物一样,既能继承一些基因,

也能丧失一些基因,那么通过不断地继承之后,可能出现一个新的类,一个功能完全不同于最初

基类的类。


5.程序能不能实现自己编程呢,程序一定要人去写吗?如果程序模仿生物,不断地进行继承,进行

进化,自我繁衍出新的类,那么是否可以实现程序的自我编程呢?未来的程序是不是可以这样写,

大致写出一个框架,然后让它自己去跑,程序在那里自适应,自己创造新的东西。


6.思考:用人类繁衍的思想,想出一个语言的设计方案。

人类两性繁殖,减数分裂,然后父母双方各一半基因的随机融合等等,这些思想是否可以做出

一个语言,可以达到这个效果。


7.上述的思考只是一个想法而已,人要有许多此类的想法,对基本的事物要敏感,要有想法,

并且对其进行一些思考,与其他发展已经很成熟的事物进行类比,万事万物都是相通的。

就像拿面向对象语言的设计去和生物进行类比,因为面向对象语言人类设计了几十年,发展其实

并不是很成熟,而生物的发展已经有几亿年,是一个很成熟的事物,所以可以将这两个事物

进行类比。



第十一天

1.真正要干成点什么事,想象力和创造力显得尤为重要。

传统教育对技术细节重视最大,在最基础的时候,技术细节确实很重要,但是达到一定高度之后,

想象力,创造力,体系性显得特别重要,是真正的突破口。


2.想象力,创造力,其实可以这样做,想到一个想法,然后就去做,去实现,然后搞出个水落石出!

不要一想而过!


3.设计类架构的方法,如果从上往下设计,也就是从基类开始思考,思考下面要派生什么类,这样的

思考方法其实是有一定问题的,很难想到下面该派生什么类。但是可以反向思考,从小往上思考。想

好最下层的类,然后不断从中抽取基类。

类是抽象出来的,不是从上往下拍脑袋想出来的。


4.一个人都优秀是他思考问题的方法优秀,有和他人不同的思考角度。一个人要快速的调转自己的思考方式,

也就是随机应变的能力强。而不是十分固执,死磕于自己的一种角度,人改变自己的思想方法很难。《柳丁思考的帽子》


5.人类一切美好高雅的东西都应该去尝试,因为这些都是艺术,是人类伟大智慧的结晶,给人带来美好感受。



第十二天

1.程序中,小问题要及时解决,不要试图去绕过去,程序越来越庞大,小问题越滚越多,最终达到毁灭性灾难。

小问题不要积累,不要去妥协,一定要及时解决。


2.读书也要有敏感性,对其里面的内容要敏感,快速抓住其思考,精髓,核心等等。