学习中科院杨力祥c++

第一天
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局部变量,全局的生存周期,局部的作用域。

11.资料:
http://blog.chinaunix.net/uid-23069658-id-3981406.html  深入理解C语言的函数调用过程
http://blog.csdn.net/wu5215080/article/details/38899259  C语言之内存四区模型和函数调用模型
http://blog.csdn.net/wu5215080/article/details/38899259  C语言之内存四区模型和函数调用模型
http://blog.chinaunix.net/uid-25667116-id-3064937.html  栈的一点理解
http://blog.csdn.net/pi9nc/article/details/12419819 程序的链接与装入(动、静态重定位)



第五天
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.读书也要有敏感性,对其里面的内容要敏感,快速抓住其思考,精髓,核心等等。




































  • 8
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值