编程是困难的,正确的使用C/C++编程尤其困难。确实,不管是C还是C++,很难看到那种良好定义并且编写规范的代码。为什么专业的程序员写出这样的代码?因为绝大部分程序员都没有深刻的理解他们所使用的语言。他们对语言的把握,有时他们知道某些东西未定义或未指定,但经常不知道为何如此。这个幻灯片,我们将研究一些小的C/C++代码片段,使用这些代码片段,我们将讨论这些伟大而充满危险的语言的基本原则,局限性,以及设计哲学。

假设你将要为你的公司招聘一名C程序言,你们公司是做嵌入式开发的,为此你要面试一些候选人。作为面试的一部分,你希望通过面试知道候选人对于C语言是否有足够深入的认识,你可以这样开始你们的谈话:

[cpp]view plaincopyprint?

1.   int main ()

2.   {

3.            int a= 42;

4.            printf(“%d\n”,a);

5.   }

int main ()

{

int a= 42;

printf(“%d\n”,a);

}

当你尝试去编译链接运行这段代码时候,会发生什么?

一个候选者可能会这样回答:

你必须通过#include<stdio.h>包含头文件,在程序的后面加上 return 0;然后编译链接,运行以后将在屏幕上打印42.

没错,这个答案非常正确。

但是另一个候选者也许会抓住机会,借此展示他对C语言有更深入的认识,他会这样回答:

你可能需要#include<stdio.h>,这个头文件显示地定义了函数printf(),这个程序经过编译链接运行,会在标准输出上输出42,并且紧接着新的一行。

然后他进一步说明:

C++编译器将会拒绝这段代码,因为C++要求必须显示定义所有的函数。然而,有一些特别的C编译器会为printf()函数创建隐式定义,把这个文件编译成目标文件。再跟标准库链接的时候,它将寻找printf()函数的定义,以此来匹配隐式的定义

因此,上面这段代码也会正常编译、链接然后运行,当然你可能会得到一些警告信息。

这位候选者乘胜追击,可能还会往下说,如果是C99,返回值被定义为给运行环境指示是否运行成功,正如C++98一样。但是对于老版本的C语言,比如说ANSI C以及K&R C,程序中的返回值将会是一些未定义的垃圾值。但是返回值通常会使用寄存器来传递,如果返回值的3,我一点都不感到惊讶,因为printf()函数的返回值是3,也就是输出到标准输出的字符个数

说到C标准,如果你要表明你关心C语言,你应该使用 intmain (void)作为你的程序入口,因为标准就这么说的。

C语言中,使用void来指示函数声明中不需要参数。如果这样声明函数int f(),那表明f()函数可以有任意多的参数,虽然你可能打算说明函数不需要参数,但这里并非你意。如果你的意思是函数不需要参数,显式的使用void,并没有什么坏处。

[cpp]view plaincopyprint?

1.   int main (void)

2.   {

3.            inta = 42;

4.            printf(“%d\n”,a);

5.   }

int main (void)

{

inta = 42;

printf(“%d\n”,a);

}

然后,有点炫耀的意思,这位候选人接着往下说:

如果你允许我有点点书生气,那么,这个程序也并不完全的符合C标准,因为C标准指出源代码必须要以新的一行结束。像这样:

[cpp]view plaincopyprint?

1.   int main ()

2.   {

3.            inta = 42;

4.            printf(“%d\n”,a);

5.   }

6.

int main ()

{

inta = 42;

printf(“%d\n”,a);

}

同时别忘了显式的声明函数printf():

[cpp]view plaincopyprint?

1.   #include <stdio.h>

2.   int main (void)

3.   {

4.            inta = 42;

5.            printf(“%d\n”,a);

6.   }

7.

#include <stdio.h>

int main (void)

{

inta = 42;

printf(“%d\n”,a);

}

现在看起来有点像C程序了,对吗?

然后,在我的机器上编译、链接并运行此程序:

[plain]view plaincopyprint?

1.   $  cc–std=c89 –c foo.c

2.   $  ccfoo.o

3.   $ ./a.out

4.   42

5.  $ echo $?  

6.  3  

7.

8.

9.   $  cc–std=c99 –c foo.c

10.    $  ccfoo.o

11.    $ ./a.out

12.    42

13.   $ echo $?  

14.   0  

$  cc–std=c89 –c foo.c

$  ccfoo.o

$ ./a.out

42

$ echo $?

3

$  cc–std=c99 –c foo.c

$  ccfoo.o

$ ./a.out

42

$ echo $?

0

这两名候选者有什么区别吗?是的,没有什么特别大的区别,但是你明显对第二个候选者的答案更满意

也许这并不是真的候选者,或许就是你的员工,呵呵。

让你的员工深入理解他们所使用的语言,对你的公司会有很大帮助吗?

让我们看看他们对于C/C++理解的有多深……

[cpp]view plaincopyprint?

1.   #include <stdio.h>

2.

3.   void foo(void)

4.   {

5.      int a = 3;

6.      ++a;

7.      printf("%d\n", a);

8.   }

9.

10.    int main(void)

11.    {

12.       foo();

13.       foo();

14.       foo();

15.    }

#include <stdio.h>

void foo(void)

{

int a = 3;

++a;

printf("%d\n", a);

}

int main(void)

{

foo();

foo();

foo();

}

这两位候选者都会是,输出三个4.然后看这段程序:

[cpp]view plaincopyprint?

1.   #include <stdio.h>

2.

3.   void foo(void)

4.   {

5.      static int a = 3;

6.      ++a;

7.      printf("%d\n", a);

8.   }

9.

10.    int main(void)

11.    {

12.       foo();

13.       foo();

14.       foo();

15.    }

16.

#include <stdio.h>

void foo(void)

{

static int a = 3;

++a;

printf("%d\n", a);

}

int main(void)

{

foo();

foo();

foo();

}

他们会说出,输出4,5,6.再看:

[cpp]view plaincopyprint?

1.   #include <stdio.h>

2.

3.   void foo(void)

4.   {

5.      static int a;

6.      ++a;

7.      printf("%d\n", a);

8.   }

9.

10.    int main(void)

11.    {

12.       foo();

13.       foo();

14.       foo();

15.    }

16.

#include <stdio.h>

void foo(void)

{

static int a;

++a;

printf("%d\n", a);

}

int main(void)

{

foo();

foo();

foo();

}

第一个候选者发出疑问,a未定义,你会得到一些垃圾值?

你说:不,会输出1,2,3.

候选者:为什么?

你:因为静态变量会被初始化未0.

第二个候选者会这样来回答:

         C标准说明,静态变量会被初始化为0,所以会输出1,2,3.

再看下面的代码片段:

[cpp]view plaincopyprint?

1.   #include <stdio.h>

2.

3.   void foo(void)

4.   {

5.      int a;

6.      ++a;

7.      printf("%d\n", a);

8.   }

9.

10.    int main(void)

11.    {

12.       foo();

13.       foo();

14.       foo();

15.    }

#include <stdio.h>

void foo(void)

{

int a;

++a;

printf("%d\n", a);

}

int main(void)

{

foo();

foo();

foo();

}

第一个候选者:你会得到1,1,1.

你:为什么你会这样想?

候选者:因为你说他会初始化为0.

你:但这不是静态变量。

候选者:哦,那你会得到垃圾值。

第二个候选者登场了,他会这样回答:

a的值没有定义,理论上你会得到三个垃圾值。但是实践中,因为自动变量一般都会在运行栈中分配,三次调用foo函数的时候,a有可能存在同一内存空间,因此你会得到三个连续的值,如果你没有进行任何编译优化的话。

你:在我的机器上,我确实得到了1,2,3.

候选者:这一点都不奇怪。如果你运行于debug模式,运行时机制会把你的栈空间全部初始化为0.

接下来的问题,为什么静态变量会被初始化为0,而自动变量却不会被初始化?

第一个候选者显然没有考虑过这个问题。

第二个候选者这样回答:

把自动变量初始化为0的代价,将会增加函数调用的代价。C语言非常注重运行速度。

然而,把全局变量区初始化为0,仅仅在程序启动时候产生成本。这也许是这个问题的主要原因

更精确的说,C++并不把静态变量初始化为0,他们有自己的默认值,对于原生类型(native types)来说,这意味着0。

再来看一段代码:

[cpp]view plaincopyprint?

1.   #include<stdio.h>

2.

3.   static int a;

4.

5.   void foo(void)

6.   {

7.       ++a;

8.       printf("%d\n", a);

9.   }

10.

11.    int main(void)

12.    {

13.        foo();

14.        foo();

15.        foo();

16.    }

17.

#include<stdio.h>

static int a;

void foo(void)

{

++a;

printf("%d\n", a);

}

int main(void)

{

foo();

foo();

foo();

}

第一个候选者:输出1,2,3.

你:好,为什么?

候选者:因为a是静态变量,会被初始化为0.

你:我同意……

候选者:cool…

这段代码呢:

[cpp]view plaincopyprint?

1.   #include<stdio.h>

2.

3.   int a;

4.

5.   void foo(void)

6.   {

7.       ++a;

8.       printf("%d\n", a);

9.   }

10.

11.    int main(void)

12.    {

13.        foo();

14.        foo();

15.        foo();

16.    }

17.

#include<stdio.h>

int a;

void foo(void)

{

++a;

printf("%d\n", a);

}

int main(void)

{

foo();

foo();

foo();

}

第一个候选者:垃圾,垃圾,垃圾。

你:你为什么这么想?

候选者:难道它还会被初始化为0?

你:是的。

候选者:那他可能输出1,2,3?

你:是的。你知道这段代码跟前面那段代码的区别吗?有static那一段。

候选者:不太确定。等等,他们的区别在于私有变量(private variables)和公有变量(public variables).

你:恩,差不多。

第二个候选者:它将打印1,2,3.变量还是静态分配,并且被初始化为0.和前面的区别:嗯。这和链接器(linker)有关。这里的变量可以被其他的编译单元访问,也就是说,链接器可以让其他的目标文件访问这个变量。但是如果加了static,那么这个变量就变成该编译单元的局部变量了,其他编译单元不可以通过链接器访问到该变量。

你:不错。接下来,将展示一些很不错的玩意。静候:)

好,接着深入理解C/C++之旅。我在翻译第一篇的时候,自己是学到不不少东西,因此打算将这整个ppt翻译完毕。

请看下面的代码片段:

[cpp]view plaincopyprint?

1.   #include <stdio.h>

2.

3.   void foo(void)

4.   {

5.       int a;

6.       printf("%d\n", a);

7.   }

8.

9.   void bar(void)

10.    {

11.        int a = 42;

12.    }

13.

14.    int main(void)

15.    {

16.        bar();

17.        foo();

18.    }

#include <stdio.h>

void foo(void)

{

int a;

printf("%d\n", a);

}

void bar(void)

{

int a = 42;

}

int main(void)

{

bar();

foo();

}

编译运行,期待输出什么呢?

[cpp]view plaincopyprint?

1.   $  cc  foo.c  &&  ./a.out

2.   42

$  cc  foo.c  &&  ./a.out

42

你可以解释一下,为什么这样吗?

第一个候选者:嗯?也许编译器为了重用有一个变量名称池。比如说,在bar函数中,使用并且释放了变量a,当foo函数需要一个整型变量a的时候,它将得到和bar函数中的a的同一内存区域。如果你在bar函数中重新命名变量a,我不觉得你会得到42的输出。

你:恩。确定。。。

第二个候选者:不错,我喜欢。你是不是希望我解释一下关于执行堆栈或是活动帧(activation frames, 操作代码在内存中的存放形式,譬如在某些系统上,一个函数在内存中以这种形式存在

ESP

形式参数

局部变量

EIP

)?

你:我想你已经证明了你理解这个问题的关键所在。但是,如果我们编译的时候,采用优化参数,或是使用别的编译器来编译,你觉得会发生什么?

候选者:如果编译优化措施参与进来,很多事情可能会发生,比如说,bar函数可能会被忽略,因为它没有产生任何作用。同时,如果foo函数会被inline,这样就没有函数调用了,那我也不感到奇怪。但是由于foo函数必须对编译器可见,所以foo函数的目标文件会被创建,以便其他的目标文件链接阶段需要链接foo函数。总之,如果我使用编译优化的话,应该会得到其他不同的值。

[cpp]view plaincopyprint?

1.  $  cc -O foo.c  &&  ./a.out  

2.  1606415608  

$  cc -O foo.c  &&  ./a.out

1606415608

候选者:垃圾值。

那么,请问,这段代码会输出什么?

[cpp]view plaincopyprint?

1.   #include <stdio.h>

2.

3.   void foo(void)

4.   {

5.      int a = 41;

6.       a= a++;

7.      printf("%d\n", a);

8.   }

9.

10.    int main(void)

11.    {

12.       foo();

13.    }

#include <stdio.h>

void foo(void)

{

int a = 41;

a= a++;

printf("%d\n", a);

}

int main(void)

{

foo();

}

第一个候选者:我没这样写过代码。

你:不错,好习惯。

候选者:但是我猜测答案是42.

你:为什么?

候选者:因为没有别的可能了。

你:确实,在我的机器上运行,确实得到了42.

候选者:对吧,嘿嘿。

你:但是这段代码,事实上属于未定义。

候选者:对,我告诉过你,我没这样写过代码。

第二个候选者登场:a会得到一个未定义的值。

你:我没有得到任何的警告信息,并且我得到了42.

候选者:那么你需要提高你的警告级别。在经过赋值和自增以后,a的值确实未定义,因为你违反了C/C++语言的根本原则中的一条,这条规则主要针对执行顺序(sequencing)的。C/C++规定,在一个序列操作中,对每一个变量,你仅仅可以更新一次。这里,a = a++;更新了两次,这样操作会导致a是一个未定义的值。

你:你的意思是,我会得到一个任意值?但是我确实得到了42.

候选者:确实,a可以是42,41,43,0,1099,或是任意值。你的机器得到42,我一点都不感到奇怪,这里还可以得到什么?或是编译前选择42作为一个未定义的值:)呵呵:)

那么,下面这段代码呢?

[cpp]view plaincopyprint?

1.   #include <stdio.h>

2.

3.   int b(void)

4.   {

5.      puts("3");

6.      return 3;

7.   }

8.

9.   int c(void)

10.    {

11.       puts("4");

12.       return 4;

13.    }

14.

15.    int main(void)

16.    {

17.       int a = b() + c();

18.       printf("%d\n", a);

19.    }

#include <stdio.h>

int b(void)

{

puts("3");

return 3;

}

int c(void)

{

puts("4");

return 4;

}

int main(void)

{

int a = b() + c();

printf("%d\n", a);

}

第一个候选者:简单,会依次打印3,4,7.

你:确实。但是也有可能是4,3,7.

候选者:啊?运算次序也是未定义?

你:准确的说,这不是未定义,而是未指定。

候选者:不管怎样,讨厌的编译器。我觉得他应该给我们警告信息。

你心里默念:警告什么?

第二个候选者:C/C++中,运算次序是未指定的对于具体的平台,由于优化的需要,编译器可以决定运算顺序,这又和执行顺序有关。

这段代码是符合C标准的。这段代码或是输出3,4,7或是输出4,3,7,这个取决于编译器。

你心里默念:要是我的大部分同事都像你这样理解他们所使用的语言,生活会多么美好:)

这个时候,我们会觉得第二个候选者对于C语言的理解,明显深刻于第一个候选者。如果你回答以上问题,你停留在什么阶段?:)

那么,试着看看第二个候选者的潜能?看看他到底有多了解C/C++

可以考察一下相关的知识:

声明和定义;

调用约定和活动帧;

序点;

内存模型;

优化;

不同C标准之间的区别;

这里,我们先分享序点以及不同C标准之间的区别相关的知识。

考虑以下这段代码,将会得到什么输出?

[cpp]view plaincopyprint?

1.   1.

2.   int a = 41;

3.   a++;

4.   printf("%d\n", a);

5.   答案:42

6.

7.  2.  

8.  int a = 41;  

9.  a++ & printf("%d\n", a);  

10.   答案:未定义  

11.

12.    3.

13.    int a = 41;

14.    a++ && printf("%d\n", a);

15.    答案:42

16.

17.    4. int a = 41;

18.    if (a++ < 42) printf("%d\n",a);

19.    答案:42

20.

21.    5.

22.   int a = 41;  

23.   a = a++;  

24.   printf("%d\n", a);  

25.   答案:未定义  

1.

int a = 41;

a++;

printf("%d\n", a);

答案:42

2.

int a = 41;

a++ & printf("%d\n", a);

答案:未定义

3.

int a = 41;

a++ && printf("%d\n", a);

答案:42

4. int a = 41;

if (a++ < 42) printf("%d\n",a);

答案:42

5.

int a = 41;

a = a++;

printf("%d\n", a);

答案:未定义

到底什么时候,C/C++语言会有副作用?

序点:

什么是序点?

简而言之,序点就是这么一个位置,在它之前所有的副作用已经发生,在它之后的所有副作用仍未开始,而两个序点之间所有的表达式或者代码执行的顺序是未定义的!

序点规则1:

在前一个序点和后一个序点之前,也就是两个序点之间,一个值最多只能被写一次;

这里,在两个序点之间,a被写了两次,因此,这种行为属于未定义。

序点规则2:

进一步说,先前的值应该是只读的,以便决定要存储什么值。

很多开发者会觉得C语言有很多序点,事实上,C语言的序点非常少。这会给编译器更大的优化空间。

接下来看看,各种C标准之间的差别:

现在让我们回到开始那两位候选者。

下面这段代码,会输出什么?

[cpp]view plaincopyprint?

1.   #include <stdio.h>

2.

3.   struct X

4.   {

5.      int a;

6.      char b;

7.      int c;

8.   };

9.

10.    int main(void)

11.    {

12.       printf("%d\n", sizeof(int));

13.       printf("%d\n", sizeof(char));

14.       printf("%d\n", sizeof(struct X));

15.    }

#include <stdio.h>

struct X

{

int a;

char b;

int c;

};

int main(void)

{

printf("%d\n", sizeof(int));

printf("%d\n", sizeof(char));

printf("%d\n", sizeof(struct X));

}

第一个候选者:它将打印出4,1,12.

你:确实,在我的机器上得到了这个结果。

候选者:当然。因为sizeof返回字节数,在32位机器上,C语言的int类型是32位,或是4个字节。char类型是一个字节长度。在struct中,本例会以4字节来对齐。

你:好。

你心里默念:do you want another ice cream?(不知道有什么特别情绪)

第二个候选者:恩。首先,先完善一下代码。sizeof的返回值类型是site_t,并不总是与int类型一样。因此,printf中的输出格式%d,不是一个很好的说明符

你:好。那么,应该使用什么格式说明符?

候选者:这有点复杂。site_t是一个无符号整型数,在32位机器上,它通常是一个无符号的int类型的数,但是在64位机器上,它通常是一个无符号的long类型的数然而,在C99中,针对site_t类型,指定了一个新的说明符,所以,%zu会是一个不多的选择

你:好。那我们先完善这个说明符的bug。你接着回答这个问题吧。

[cpp]view plaincopyprint?

1.   #include <stdio.h>

2.

3.   struct X

4.   {

5.      int a;

6.      char b;

7.      int c;

8.   };

9.

10.    int main(void)

11.    {

12.       printf("%zu\n", sizeof(int));

13.       printf("%zu\n", sizeof(char));

14.       printf("%zu\n", sizeof(struct X));

15.    }

16.

#include <stdio.h>

struct X

{

int a;

char b;

int c;

};

int main(void)

{

printf("%zu\n", sizeof(int));

printf("%zu\n", sizeof(char));

printf("%zu\n", sizeof(struct X));

}

候选者:这取决与平台,以及编译时的选项。唯一可以确定的是,sizeof(char)是1.你要假设在64位机器上运行吗?

你:是的。我有一台64位的机器,运行在32位兼容模式下。

候选者:那么由于字节对齐的原因,我觉得答案应该是4,1,12.当然,这也取决于你的编译选项参数,它可能是4,1,9.如果你在使用gcc编译的时候,加上-fpack-struct,来明确要求编译器压缩struct的话

你:在我的机器上确实得到了4,1,12。为什么是12呢?

候选者:工作在字节不对齐的情况下,代价非常昂贵。因此编译器会优化数据的存放,使得每一个数据域都以字边界开始存放。struct的存放也会考虑字节对齐的情况。

你:为什么工作在字节不对齐的情况下,代价会很昂贵?

候选者:大多数处理器的指令集都在从内存到cpu拷贝一个字长的数据方面做了优化。如果你需要改变一个横跨字边界的值,你需要读取两个字,屏蔽掉其他值,然后改变再写回。可能慢了10不止。记住,C语言很注意运行速度。

你:如果我得struct上加一个char d,会怎么样?

候选者:如果你把char d加在struct的后面,我预计sizeof(struct X)会是16.因为,如果你得到一个长度为13字节的结构体,貌似不是一个很有效的长度。但是,如果你把char d加在char  b的后面,那么12会是一个更为合理的答案。

你:为什么编译器不重排结构体中的数据顺序,以便更好的优化内存使用和运行速度?

候选者:确实有一些语言这样做了,但是C/C++没有这样做

你:如果我在结构体的后面加上char *d,会怎么样?

候选者:你刚才说你的运行时环境是64位,因此一个指针的长度的8个字节。也许struct的长度是20?但是另一种可能是,64位的指针需要在在效率上对齐,因此,代码可能会输出4,1,24

你:不错。我不关心在我的机器上会得到什么结果,但是我喜欢你的观点以及洞察力J

第二位候选者表现不错,那么,相比大多数程序员,他还有什么潜力没有被挖掘呢?

可以从以下几个角度去考察:

有关平台的问题—32位与64位的编程经验;

内存对齐;

CPU以及内存优化;

C语言的精髓;

接下来,主要分享一下以下相关内容:

内存模型;

优化;

C语言之精髓;

内存模型:

静态存储区(static storage):如果一个对象的标识符被声明为具有内部链接或是外部链接,或是存储类型说明符是static,那么这个对象具有静态生存期。这个对象的生命周期是整个程序的运行周期。

PS:内部链接,也就是编译单元内可见,是需要使用static来修饰的,连接程序不可见;外部链接,是指别的编译单元可见,也就是链接程序可见。我这里还不太清楚为什么需要三种情况来说明。

[cpp]view plaincopyprint?

1.   int* immortal(void)

2.   {

3.       static int storage = 42;

4.       return &storage;

5.   }

int* immortal(void)

{

static int storage = 42;

return &storage;

}

自动存储区(automatic storage):如果一个对象没有被指明是内部链接还是外部链接,并且也没有static修饰,那么,这个对象具有自动生存期,也称之为本地生存期。一般使用auto说明符来修饰,只在块内的变量声明中允许使用,这样是默认的情况,因此,很少看到auto说明符。简单地说,自动存储区的变量,在一对{}之间有效。

[cpp]view plaincopyprint?

1.   int* zombie(void)

2.   {

3.       auto int storage = 42;

4.       return &storage;

5.   }

int* zombie(void)

{

auto int storage = 42;

return &storage;

}

分配的存储区域(allocated storage):调用calloc函数,malloc函数,realloc函数分配的内存,称之为分配的存储区域。他们的作用域(生命周期会是更好的术语吗?)在分配和释放之间。

[cpp]view plaincopyprint?

1.   int* finite(void)

2.   {

3.       int* ptr = malloc(sizeof(int*));

4.       *ptr = 42;

5.       return ptr;

6.   }

int* finite(void)

{

int* ptr = malloc(sizeof(int*));

*ptr = 42;

return ptr;

}

优化相关:

一般来说,编译的时候,你都应该打开优化选项。强制编译器更努力的去发现更多的潜在的问题

上面,同样地代码,打开优化选项的编译器得到了警告信息:a没有初始化。

Deep C (and C++) by Olve Maudal and Jon Jagger— 很不错的国外技术文章相关推荐

  1. Deep C (and C++) by Olve Maudal and Jon Jagger

    作者:Rockics 来源:http://blog.csdn.net/rockics/article/details/7015067 说明:译自Deep C (and C++) by Olve Mau ...

  2. [转]深入理解C/C++ [Deep C (and C++)]

    作者:Rockics 来源:http://blog.csdn.net/rockics/article/details/7015067 说明:译自Deep C (and C++) by Olve Mau ...

  3. 心得丨老生常谈:普通程序员到底如何入门深度学习?

    作为一名软件工程师,我们应该活到老学到老,时刻与不断发展的框架.标准和范式保持同步.同时,还要能活学活用,在工作中使用最合适的工具,以提高工作效率.随着机器学习在越来越多的应用程序中寻得了一席之地,越 ...

  4. Python-pillow库显示MNIST图片的方法

    文章目录 写在前面 示例代码 另一种办法 小结 参考 写在前面 之前一直在看MLP的手写数字识别问题,看的书是迈克尔·尼尔森的深度学习入门书<神经网络与深度学习>(<Neural n ...

  5. 机器学习从入门到进阶✅

    1. 放弃海量资料!!! 没错,就是放弃海量资料!在我们想要入门机器学习的时候,往往会搜集很多资料,什么 xx学院机器学习内部资源.机器学习从入门到进阶百 G 资源.xx 人工智能教程,等等.很多时候 ...

  6. 机器学习三个部分:输入、算法、输出 资料收集

    机器学习三个部分:输入.算法.输出. 输入:驱动机器学习的数据 输入是训练和算法需要的数据集.从源代码到统计数据,数据集可以包含任何东西: GSA / data(美国总务管理局数据):https:// ...

  7. 最容易读进去的深度学习科普贴

    注:自己写专业论文时候觉得很不错的一篇文章,分享在博客里 (一) 一 2016 年一月底,人工智能的研究领域,发生了两件大事. 先是一月二十四号,MIT 的教授,人工智能研究的先驱者,Marvin M ...

  8. [研究方向]什么是深度学习?它到底有多火?

    本文转自CSDN,作者Anymake 本文是一篇供程序员了解的深度学习入门文章.由于目前深度学习很火,也是考研的热门研究方向之一,于是就提供给同学们啦~ 作为一名软件工程师,我们应该活到老学到老,时刻 ...

  9. 普通程序员如何入门深度学习?

    摘要: 作为一名软件工程师,我们应该活到老学到老,时刻与不断发展的框架.标准和范式保持同步.同时,还要能活学活用,在工作中使用最合适的工具,以提高工作效率.随着机器学习在越来越多的应用程序中寻得了一席 ...

最新文章

  1. Python - 输出格式 (学习小结)
  2. python将图片转化为字符图
  3. 从2017年顶会论文看Attention Model - PaperWeekly 第50期
  4. 大端和小端的判断及转换
  5. 【今晚7点半】:GVoice 千万在线语音传输的那些事
  6. 1.java的基础和数据类型
  7. 写出高效优美的C语言代码(单片机)
  8. Autofs自动挂在实现
  9. 梅宏:不容错过的大数据时代_我们错过了整个网络支付领域:如何为创作者修复网络...
  10. Wps的两种论文标注参考文献
  11. c语言输入12行怎么输入,c语言中,定义什么型别的变数能同时储存数字跟字元,怎么输入...
  12. royer推挽自激电路
  13. python教程99--控制鼠标键盘模块 pyautogui
  14. 悲观锁和乐观锁的区别及使用场景
  15. pc页面样式自适应的几种方案
  16. 内部总线、系统总线、外部总线区别
  17. 【Axure10基础教程】第七章 设置文本
  18. Unity 中实现子弹时间效果
  19. SMVC4: JSON文件的使用
  20. [超详细]微信小程序使用iconfont教程及解决真机无法显示问题

热门文章

  1. 计算机word.实训报告体会,计算机实训报告【五篇】.docx
  2. 论文阅读--Adapted Dynamic Memory Network for Emotion Recognition in Conversation
  3. WPM2026 P沟道增强型MOS场效应晶体管
  4. 记录达梦一次IO问题的分析
  5. 杨辉三角 c语言 二维数组
  6. 2021年甘肃省安全员C证报名考试及甘肃省安全员C证考试报名
  7. Android 6.0 指纹识别功能学习(一)----阿冬专栏!!!
  8. 随鼠标滚轮缩小和放大图片
  9. 软件设计师(软考中级)考试大纲
  10. VS2008里的代码如何格式化?