深入理解C语言中的指针与数组之指针篇(转载)

时间:2022-05-28 22:26:46

前言

其实很早就想要写一篇关于指针和数组的文章,毕竟可以认为这是C语言的根本所在。相信,任意一家公司如果想要考察一个人对C语言的理解,指针和数组绝对是必考的一部分。

但是之前一方面之前一直在忙各种事情,一直没有时间静下心来写这些东西,毕竟这确实是一件非常耗费时间和精力的事情;一方面,个人对C语言的掌握和理解也还有限,怕写出来的东西会对大家造成误导。当然,今天写的这些东西也肯定存在各种问题,不严谨甚至错误的地方肯定有,也希望大家来共同探讨,相互改进。

我会慢慢的写完这几章,有想法的童鞋可以和我探讨。

指针

预备知识

在深入理解指针之前,我认为有必要先复习或者学习一下计算机原理的基础知识。

计算机是如何从内存中进行取指的?

计算机的总线可以分为3种:数据总线,地址总线和控制总线。这里不对控制总线进行描述。数据总线用于进行数据信息传送。数据总线的位数一般与CPU的字长一致。一般而言,数据总线的位数跟当前机器int值的长度相等。例如在16位机器上,int的长度是16bit,32位机器则是32bit。这个计算机一条指令最多能够读取或者存取的数据长度。大于这个值,计算机将进行多次访问。这也就是我们说的64位机器进行64位数据运算的效率比32位要高的原因,因为32位机要进行两次取指和运行,而64位机却只需要一次!

地址总线专门用于寻址,CPU通过该地址进行数据的访问,然后把处于该地址处的数据通过数据总线进行传送,传送的长度就是数据总线的位数。地址总线的位数决定了CPU可直接寻址的内存空间大小,比如CPU总线长32位,其最大的直接寻址空间长232KB,也就是4G。这也就是我们常说的32位CPU最大支持的内存上限为4G(当然,实际上支持不到这个值,因为一部分寻址空间会被映射到外部的一些IO设备和虚拟内存上。现在通过一些新的技术,可以使32位机支持4G以上内存,但这个不在这里的讨论范围内)。

一般而言,计算机的地址总线和数据总线的宽度是一样的,我们说32位的CPU,数据总线和地址总线的宽度都是32位。

         计算机访问某个数据的时候,首先要通过地址总线传送数据存储或者读取的位置,然后在通过数据总线传送需要存储或者读取的数据。一般地,int整型的位数等于数据总线的宽度,指针的位数等于地址总线的宽度。

 

计算机的基本访问单元

学过C语言的人都知道,C语言的基本数据类型中,就属char的位数最小,是8位。我们可以认为计算机以8位,即1个字节为基本访问单元。小于一个字节的数据,必须通过位操作来进行访问。

内存访问方式

如图1所示,计算机在进行数据访问的时候,是以字节为基本单元进行访问的,所以可以认为,计算每次都是从第p个字节开始访问的。访问的长度将由编译器根据实际类型进行计算,这在后面将会进行讲述。

深入理解C语言中的指针与数组之指针篇(转载)

图1 内存访问方式

想要了解更多,就去翻阅计算机组成原理和编译原理吧。

sizeof关键字

         sizeof关键字是编译器用来计算某些类型的数据的长度的,以字节为基本单位。例如:

sizeof(char)=1;

sizeof(int)=4;

         sizeof(Type)的值是在编译的时候就计算出来了的,可以认为这是一个常量!

什么是指针

指针其实就是数据存放的地址,图1中的p就是一个指针。在图1中,n一般是CPU的位数,32位机上,n=32。因为指针需要能够指向内存中的任意一个位置,因此,指针的长度应该是n位的,32位机器上指针长度就是32位。这和整型的长度是相等的!

         在我个人的理解中,可以将指针理解成int整型,只不过它存放的数据是内存地址,而不是普通数据,我们通过这个地址值进行数据的访问,假设它的是p,意思就是该数据存放位置为内存的第p个字节。

当然,我们不能像对int类型的数据那样进行各种加减乘除操作,这是编译器不允许的,因为这样错是非常危险的!

图2就是对指针的描述,指针的值是数据存放地址,因此,我们说,指针指向数据的存放位置。

深入理解C语言中的指针与数组之指针篇(转载)

图2 指针

指针的长度

我们使用这样的方式来定义一个指针:

Type *p;

我们说p是指向type类型的指针,type可以是任意类型,除了可以是char,short, int, long等基本类型外,还可以是指针类型,例如int *, int **, 或者更多级的指针,也可是是结构体,类或者函数等。于是,我们说:

int * 是指向int类型的指针;

int **,也即(int *) *,是指向int *类型的指针,也就是指向指针的指针;

int ***,也即(int **) *,是指向int**类型的指针,也就是指向指针的指针的指针;

…我想你应该懂了

struct xxx *,是指向struct xxx类型的指针;

其实,说这么多,只是希望大家在看到指针的时候,不要被int ***这样的东西吓到,就像前面说的,指针就是指向某种类型的指针,我们只看最后一个*号,前面的只不过是type类型罢了。

细心一点的人应该发现了,在“什么是指针”这一小节当中,已经表明了:指针的长度跟CPU的位数相等,大部分的CPU是32位的,因此我们说,指针的长度是32bit,也就是4个字节!注意:任意指针的长度都是4个字节,不管是什么指针!(当然64位机自己去测一下,应该是8个字节吧。。。)

于是:

Type *p;

sizeof(p)的值是4,Type可以是任意类型,char,int, long, struct, class, int **…

         以后大家看到什么sizeof(char*), sizeof(int *),sizeof(xxx *),不要理会,统统写4,只要是指针,长度就是4个字节,绝对不要被type类型迷惑!至于type是干什么用的,这个是给编译器用的,用于指针运算,这个在下面的章节中会有详细介绍。

取地址

我们说指针指向的是数据的存放地址,因此指针的值等于数据的存放地址。那么给指针赋值的时候就需要进行数据的取地址操作,这个我想不用我多说,各位也知道是&符号,没错,是&符号。

我们可以这样取地址:

Type v,*p=&v;

当然也可以:

Type v, *p;(或者Type v; Type *p)

p=&v;

这里的Type依然是任意的类型,可以是N级指针、结构体、类或者函数什么的。

指针运算

N多的面试会考这种东西了:

Type *p;

p++;

然后问你p的值变化了多少。

其实,也可以认为这是在考编译器的基本知识。因此p的值并不像表面看到的+1那么简单,编译器实际上对p进行的是加sizeof(Type)的操作。

看一个一段代码的测试结果:

  1. char cv='a',*pcv=&cv;
  2. short sv=1, *psv=&sv;
  3. int iv=1, *piv=&iv;
  4. long lv=1, *plv=&lv;
  5. long long llv=1, *pllv=&llv;
  6. float fv=1.0, *pfv=&fv;
  7. double dv=1.0, *pdv=&dv;
  8. long double ldv=1.0, *pldv=&ldv;
  9. //cout<<"pcv:"<<pcv<<" pcv+1: "<<pcv+1<<endl;
  10. cout<<"psv:"<<psv<<" psv+1: "<<psv+1<<endl;
  11. cout<<"piv:"<<piv<<" piv+1: "<<piv+1<<endl;
  12. cout<<"plv:"<<plv<<" plv+1: "<<plv+1<<endl;
  13. cout<<"pllv:"<<pllv<<" pllv+1: "<<pllv+1<<endl;
  14. cout<<"pfv:"<<pfv<<" pfv+1: "<<pfv+1<<endl;
  15. cout<<"pdv:"<<pdv<<" pdv+1: "<<pdv+1<<endl;
  16. cout<<"pldv:"<<pldv<<" pldv+1: "<<pldv+1<<endl;
  17. cout<<endl;
char cv='a',*pcv=&cv;
short sv=1, *psv=&sv;
int iv=1, *piv=&iv;
long lv=1, *plv=&lv;
long long llv=1, *pllv=&llv;
float fv=1.0, *pfv=&fv;
double dv=1.0, *pdv=&dv;
long double ldv=1.0, *pldv=&ldv; //cout<<"pcv:"<<pcv<<" pcv+1: "<<pcv+1<<endl;
cout<<"psv:"<<psv<<" psv+1: "<<psv+1<<endl;
cout<<"piv:"<<piv<<" piv+1: "<<piv+1<<endl;
cout<<"plv:"<<plv<<" plv+1: "<<plv+1<<endl;
cout<<"pllv:"<<pllv<<" pllv+1: "<<pllv+1<<endl;
cout<<"pfv:"<<pfv<<" pfv+1: "<<pfv+1<<endl;
cout<<"pdv:"<<pdv<<" pdv+1: "<<pdv+1<<endl;
cout<<"pldv:"<<pldv<<" pldv+1: "<<pldv+1<<endl;
cout<<endl;

(这里注释掉char一行的原因是因为cout<<(char*)会被当成字符串输出,而不是char的地址)

执行结果:

深入理解C语言中的指针与数组之指针篇(转载)

观察结果,可以看出,他们的增长结果分别是:

2(sizeof(short))

4(sizeof(int))

4(sizeof(long))

8(sizeof(long long))

4(sizeof(float))

8(sizeof(double))

12(sizeof(long double))

喏,增加的值是不是sizeof(Type)呢?别的什么struct,class之类的,就不验证你,有兴趣的自己去验证。

我们再对这样的一段代码进行汇编,查看编译器是如何进行指针的加法操作的:

  1. int iv=1,*piv=&iv;
  2. piv++;
  3. cout<<piv<<endl;
  4. piv=piv+4;
  5. cout<<piv<<endl;
  6. cout<<endl;
int iv=1,*piv=&iv;
piv++;
cout<<piv<<endl;
piv=piv+4;
cout<<piv<<endl;
cout<<endl;

汇编结果:

  1. call    ___main
  2. movl    $1, -12(%ebp)
  3. leal    -12(%ebp), %eax
  4. movl    %eax, -8(%ebp)
  5. addl    $4, -8(%ebp)  /** 这里是piv++ **/
  6. movl    -8(%ebp), %eax
  7. movl    %eax, 4(%esp)
  8. movl    $__ZSt4cout, (%esp)
  9. call    __ZNSolsEPKv
  10. movl    $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
  11. movl    %eax, (%esp)
  12. call    __ZNSolsEPFRSoS_E
  13. addl    $16, -8(%ebp)  /** 这里是piv+4 **/
  14. movl    -8(%ebp), %eax
  15. movl    %eax, 4(%esp)
  16. movl    $__ZSt4cout, (%esp)
  17. call    __ZNSolsEPKv
  18. movl    $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
  19. movl    %eax, (%esp)
  20. call    __ZNSolsEPFRSoS_E
  21. movl    $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
  22. movl    $__ZSt4cout, (%esp)
  23. call    __ZNSolsEPFRSoS_E
  24. movl    $0, %eax
  25. addl    $36, %esp
  26. popl    %ecx
  27. popl    %ebp
  28. leal    -4(%ecx), %esp
  29. ret
call    ___main
movl $1, -12(%ebp)
leal -12(%ebp), %eax
movl %eax, -8(%ebp)
addl $4, -8(%ebp) /** 这里是piv++ **/
movl -8(%ebp), %eax
movl %eax, 4(%esp)
movl $__ZSt4cout, (%esp)
call __ZNSolsEPKv
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
movl %eax, (%esp)
call __ZNSolsEPFRSoS_E
addl $16, -8(%ebp) /** 这里是piv+4 **/
movl -8(%ebp), %eax
movl %eax, 4(%esp)
movl $__ZSt4cout, (%esp)
call __ZNSolsEPKv
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
movl %eax, (%esp)
call __ZNSolsEPFRSoS_E
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
movl $__ZSt4cout, (%esp)
call __ZNSolsEPFRSoS_E
movl $0, %eax
addl $36, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret

          注意看注释部分的结果,我们看到,piv的值显示加了4(sizeof(int)),然后又加了16(4*sizeof(int))。

         总结一点:

         指针的实际运算,将会由编译器在编译的时候,根据指针指向数据类型的大小进行实际的翻译转换。指针类型的作用就在于此,让编译器能够正确的翻译这些指令的操作,另一方面,也让编译器检查程序员对指针的操作是否合法,保证程序的正确性和健壮性。

         Type *p;  p=p+i;

         最终p的值实际上是(value of p) + i*sizeof(Type);

         Type *p;  p=p-i;

         最终p的值实际上是(value of p) - i*sizeof(Type);

         注意:指针只能进行加法和减法操作,不能进行乘除法!(指针毕竟不是普通的整数,乘除法的跨度太大了,出发还会搞出小数点神马的,这是我个人的理解。但是编译器不允许进行指针的乘除法。)

NULL指针

NULL是C语言标准定义的一个值,这个值其实就是0,只不过为了使得看起来更加具有意义,才定义了这样的一个宏,中文的意思是空,表明不指向任何东西。你懂得。不过这里不讨论空和零的区别,呵呵。

在C语言中,NULL其实就是0,就像前面说的指针可以理解成特殊的int,它总是有值的,p=NULL,其实就是p的值等于0。对于不多数机器而言,0地址是不能直接访问的,设置为0,就表示该指针哪里都没指向。

当然,就机器内部而言,NULL指针的实际值可能与此不同,这种情况下,编译器将负责零值和内部值之间的翻译转换。

NULL指针的概念非常有用,它给了你一种方法,表示某个特定的指针目前并未指向任何东西。例如,一个用于在某个数组中查找某个特定值的函数可能返回一个指向查找到的数组元素的指针。如果没找到,则返回一个NULL指针。

在内存的动态分配上,NULL的意义非同凡响,我们使用它来避免内存被多次释放,造成经常性的段错误(segmentation fault)。一般,在free或者delete掉动态分配的内存后,都应该立即把指针置空,避免出现所以的悬挂指针,致使出现各种内存错误!例如:

  1. int *p=(int*)malloc(sizeof(int));
  2. *p=23;
  3. free(p);
  4. p=NULL;
int *p=(int*)malloc(sizeof(int));
*p=23;
free(p);
p=NULL;

free函数是不会也不可能把p置空的。像下面这样的代码就会出现内存段错误:

  1. int *p=(int*)malloc(sizeof(int));
  2. *p=23;
  3. free(p);
  4. free(p);
int *p=(int*)malloc(sizeof(int));
*p=23;
free(p);
free(p);

因为,第一次free操作之后,p指向的内存已经释放了,但是p的值还没有变化,free函数改不了这个值,再free一次的时候,p指向的内存区域已经被释放了,这个地址已经变成了非法地址,这个操作将导致段错误的发生(此时,p指向的区域刚好又被分配出去了,但是这种概率非常低,而且对这样一块内存区域进行操作是非常危险的!)

但是下面这段代码就不会出现这样的问题:

  1. int *p=(int*)malloc(sizeof(int));
  2. *p=23;
  3. free(p);
  4. p=NULL;
  5. free(p);
int *p=(int*)malloc(sizeof(int));
*p=23;
free(p);
p=NULL;
free(p);

因为p的值编程了NULL,free函数检测到p为NULL,会直接返回,而不会发生错误。

         这里顺便告诉大家一个内存释放的小窍门,可以有效的避免因为忘记对指针进行置空而出现各种内存问题。这个方法就是自定义一个内存释放函数,但是传入的参数不知指针,而是指针的地址,在这个函数里面置空,如下:

  1. #include<iostream>
  2. #include<stdlib.h>
  3. using namespace std;
  4. void my_free(void *p){
  5. void **tp=(void **)p;
  6. if(NULL==*tp)
  7. return ;
  8. free(*tp);
  9. *tp=NULL;
  10. }
  11. int main(int argc, char **argv){
  12. int *p=new int;
  13. *p=1;
  14. cout<<p<<endl;
  15. my_free(&p);
  16. cout<<p<<endl;
  17. free(p);
  18. return 0;
  19. }
#include<iostream>
#include<stdlib.h>
using namespace std; void my_free(void *p){
void **tp=(void **)p;
if(NULL==*tp)
return ;
free(*tp);
*tp=NULL;
} int main(int argc, char **argv){
int *p=new int;
*p=1;
cout<<p<<endl;
my_free(&p);
cout<<p<<endl;
free(p);
return 0;
}

结果:

深入理解C语言中的指针与数组之指针篇(转载)

my_free调用了之后,p的值就变成了0(NULL),调用多少次free都不会报错了!

         另外一个方式也非常有效,那就是定义FREE宏,在宏里面对他进行置空。例如

  1. #include<iostream>
  2. #include<stdlib.h>
  3. using namespacestd;
  4. #define FREE(x) if(x) free(x);  x=NULL
  5. int main(intargc, char **argv){
  6. int *p=new int;
  7. *p=1;
  8. cout<<p<<endl;
  9. FREE(p);
  10. cout<<p<<endl;
  11. free(p);
  12. return 0;
  13. }
#include<iostream>
#include<stdlib.h>
using namespacestd; #define FREE(x) if(x) free(x); x=NULL int main(intargc, char **argv){
int *p=new int;
*p=1;
cout<<p<<endl;
FREE(p);
cout<<p<<endl;
free(p);
return 0;
}

执行结果同上面一样,不会报段错误:

深入理解C语言中的指针与数组之指针篇(转载)

(关于内存的动态分配,这是个比较复杂的话题,有机会再专门开辟一章给各位讲述一下吧,写个帖子还是很花费时间和精力的,呵呵,写过的童鞋应该都很清楚,所以顺便插一句,转帖可以,请注明出处,毕竟,大家都是本着共享的精神来讨论问题的,写的好坏都没有向你所要什么,请尊重每个人的劳动成果。)

void指针

虽然从字面上看,void的意思是空,但是void指针的意思,可不是空指针的意思,空指针指的是上面所说的NULL指针。

         void指针实际上的意思是指向任意类型的指针。任意类型的指针都可以直接赋给void指针,而不需要进行强制转换。

例如:

Type a, *p=&a;(Type等于char, int, struct, int *…)

void *pv;

pv=p;

就像前面说的,void指针的好处,就在于,任意的指针都可以直接赋值给它,这在某些场合非常有用,因此有些操作对于任意指针都是相同的。void指针最常用于内存管理。最典型的,也是大家最熟知的,就是标准库的free函数。它的原型如下:

void free(void*ptr);

free函数的参数可以是任意指针,没有谁见过free参数里面的指针需要强壮为void*的吧?

malloc, calloc,realloc这些函数的返回值也是void指针,因为内存分配,实际上只需要知道分配的大小,然后返回新分配内存的地址就可以了,指针的值就是地址,返回的不管是何种指针,其实结果都是一样的,因为所有的指针长度其实都是32位的(32位机器),它的值就是内存的地址,指针类型只是给编译器看的,目的是让编译器在编译的时候能够正确的设置指针的值(参见指针运算章节)。如果malloc函数设置成下面这样的原型,完全没有问题。

char*malloc(size_t sz);

实际上设置成

Type*malloc(size_t sz);

也是完全正确的,使用void指针的原因,实际上就像前面说的,void指针意思是任意指针,这样设计更加严谨一些,也更符合我们的直观理解。如果对前面我说的指针概念理解的童鞋,肯定明白这一点。

未初始化和非法指针

经常有面试,会考这样的代码校错:

int *a;

*a=12;

这段代码,在*a=12这里出了问题。这里的问题就在于,a究竟指向哪里?我们声明了这个变量,但是从未对它进行初始化,一般而言,没有初始化,a的值是任意的,随机的。如果a是全局变量或者static类型,它会被初始化为0(前面说过,其实指针可以理解成值是内存地址的int),但是不管哪种方式,这种方式的赋值都是非常危险的,如果你有着中体彩头号彩票的运气,a的值刚好等于某个变量或者分配内存的地址,那么这里的运行不会报错,但这时候的运气却不是什么好运,相反,是非常倒霉!因为这是对一块不属于你的内存进行操作,这实在是太危险了!如果a的初始值是个非法地址,这个赋值语句在执行的时候将会报错,从而终止程序吗,这个错误同样是段错误(segmentation fault),如果是这样,你是幸运的,因为你发现了它,这样就可以修正它。

关于这种问题,编译器可能会,也可能不会对它进行检测。GNU的编译器是会进行检测的,会对未初始化的指针或变量输出警告信息。

多级指针(也叫指针的指针)

其实如果对前面的指针概念完全理解了,这里都可以略过。指针的指针,无非就是指针指向的数据类型是指针罢了。

Type *p;

其中Type类型是指针,比如可以是int*,也可以是int **,这样p对应的就是二级指针和三级指针。一级指针的值存放的是数据的地址,二级指针的值存放的一级指针的地址,三级指针的值存放的是二级指针的地址,依此类推…

函数指针

跟普通的变量一样,每一个函数都是有其地址的,我们通过跳转到这个地址执行代码来进行函数调用,只是,跟取普通数据不同的在于,函数有参数和返回值,在进行函数调用的时候,首先需要将参数压入栈中,调用完成后又需要将参数压入栈中。既然函数也是通过地址来进行访问的,那它也可以使用指针来指向,事实上,每一个函数名都是一个指针,不过它是指针常量和指针常量,它的值是不能改的,指向的值也不能改。

(关于常量指针和指针常量什么的,有时间在专门开辟一章来说明const这个东东吧,也是很有讲头的一个东东。。。)

函数指针一般用来干什么呢?函数指针最常用的场合就是回调函数。回调函数,顾名思义,就是某个函数会在适当的时候被别人调用。当期望你调用的函数能够使用你的某些方式去操作的时候,回调函数就很有用,比如,你期望某个排序函数在比较的时候,能够使用你定义的比较方法去比较。

有过较深入的C编程经验的人应该都接触过。C的标准库中就有使用,例如在strlib.h头文件的qsort函数,它的原型为:

void qsort(void*__base, size_t __nmemb, size_t __size, int(*_compar)(const void *, const void*));

其中int(*_compar)(const void *, const void *)就是回调函数,这个函数用于qsort函数用于数据的比较。下面,我会举一个例子,来描述qsort函数的工作原理。

一般,我们使用下面这样的方式来定义函数指针:

typedef int(*compare)(const void *x, const void *y);

这个时候,compare就是参数为const void *, const void *类型,返回值是int类型的函数。例如:

  1. typedef int (*compare)(const void *x, const void *y);
  2. int my_compare(const void *x, const void *y){
  3. const int *a=(int *)x;
  4. const int *b=(int *)y;
  5. if(*a>*b)
  6. return 1;
  7. if(*a==*b)
  8. return 0;
  9. return -1;
  10. }
  11. void my_sort(void *data, int length, int size, compare){
  12. char *d1,*d2;
  13. //do something
  14. if(compare(d1,d2)<0){
  15. //do something
  16. }else if(compare(d1,d2)==0){
  17. //do something
  18. }
  19. else{
  20. //do something
  21. }
  22. //do something
  23. }
  24. int main(int argc, char **argv){
  25. int arr={2,4,3,656,23};
  26. my_sort(arr, 5, sizeof(int), my_compare);
  27. //do something
  28. return 0;
  29. }
typedef int (*compare)(const void *x, const void *y);

int my_compare(const void *x, const void *y){
const int *a=(int *)x;
const int *b=(int *)y;
if(*a>*b)
return 1;
if(*a==*b)
return 0;
return -1;
} void my_sort(void *data, int length, int size, compare){
char *d1,*d2;
//do something
if(compare(d1,d2)<0){
//do something
}else if(compare(d1,d2)==0){
//do something
}
else{
//do something
}
//do something
} int main(int argc, char **argv){
int arr={2,4,3,656,23};
my_sort(arr, 5, sizeof(int), my_compare);
//do something
return 0;
}

用typedef来定义的好处,就是可以使用一个简短的名称来表示一种类型,而不需要总是使用很长的代码来,这样不仅使得代码更加简洁易读,更是避免了代码敲写容易出错的问题。强烈推荐各位在定义结构体,指针(尤其是函数指针)等比较复杂的结构时,使用typedef来定义。

后序

         关于指针与数组的部分将在后续介绍,多级指针在数组中的应用也会更加深入的介绍

 

深入理解C语言中的指针与数组之指针篇(转载)的更多相关文章

  1. C语言中的函数、数组与指针

    1.函数:当程序很小的时候,我们可以使用一个main函数就能搞定,但当程序变大的时候,就超出了人的大脑承受范围,逻辑不清了,这时候就需要把一个大程序分成许多小的模块来组织,于是就出现了函数概念:  函 ...

  2. 理解C语言中指针的声明以及复杂声明的语法

    昨天刚把<C程序设计语言>中"指针与数组"章节读完,最终把心中的疑惑彻底解开了.如今记录下我对指针声明的理解.顺便说下怎样在C语言中创建复杂声明以及读懂复杂声明. 本文 ...

  3. 这样子来理解C语言中指针的指针

    友情提示:阅读本文前,请先参考我的之前的文章<从四个属性的角度来理解C语言的指针也许会更好理解>,若已阅读,请继续往下看. 我从4个属性的角度来总结了C语言中的指针概念.对于C语言的一个指 ...

  4. 【转载】理解C语言中的关键字extern

    原文:理解C语言中的关键字extern 最近写了一段C程序,编译时出现变量重复定义的错误,自己查看没发现错误.使用Google发现,自己对extern理解不透彻,我搜到了这篇文章,写得不错.我拙劣的翻 ...

  5. 深入理解C语言中的指针与数组之指针篇

    转载于http://blog.csdn.net/hinyunsin/article/details/6662851     前言 其实很早就想要写一篇关于指针和数组的文章,毕竟可以认为这是C语言的根本 ...

  6. 理解C语言中几个常见修饰符

    写在前面 今天下午一个同事问「register」关键字是什么作用?噢,你说的是「register」啊,它的作用是……脑袋突然断片儿,我擦,啥意思来着,这么熟悉的陌生感.做C语言开发时间也不短了,不过好 ...

  7. C语言学习笔记之成员数组和指针

    成员数组和指针是我们c语言中一个非常重要的知识点,记得以前在大学时老师一直要我们做这类的练习了,但是最的还是忘记了,今天来恶补一下.     单看这文章的标题,你可能会觉得好像没什么意思.你先别下这个 ...

  8. C语言中结构体赋值问题的讨论(转载)

    今天帮师姐调一个程序的BUG,师姐的程序中有个结构体直接赋值的语句,在我印象中结构体好像是不能直接赋值的,正如数组不能直接赋值那样,我怀疑这个地方有问题,但最后证明并不是这个问题.那么就总结一下C语言 ...

  9. C语言指针系列 - 一级指针&period;一维数组&comma;二级指针&comma;二维数组&comma;指针数组&comma;数组指针&comma;函数指针&comma;指针函数

    1. 数组名 C语言中的数组名是一个特殊的存在, 从本质上来讲, 数组名是一个地址, 我们可以打印一个指针的值,和打印一个数组的值来观察出这个本质: int nArray[10] ={ 0 }; in ...

随机推荐

  1. 2016第二届陕西省网络空间安全大赛WriteUp

    2016年5月28号(正式比赛) 有选择题和实践题,俩队员在弄选择题时,我去拿了web1的一血. 0x01 web 是一道代码审计题,发包,返回了源代码: <?php if (isset($_G ...

  2. thinkphp全站静态页实现方法!

    1:在根目录下的全局index.php中加下面这行: define('HTML_PATH', './htm');//生成静态页面的文件位置 2:在项目的配置文件config.php中加下面这行: 'H ...

  3. 51nod 1174 1174 区间中最大的数

    题目链接:51nod 1174 1174 区间中最大的数 ST(Sparse Table)算法学习参考博客:http://blog.csdn.net/niushuai666/article/detai ...

  4. javascript&lowbar;this的用法

    javascript : this的用法 1.this代表全局对象 2.作为函数对象的公共方法(new对象后,可以调用带this关键字的属性) 总结:如果在javascript语言里没有通过new(包 ...

  5. css 时钟

    (转自:http://www.cnblogs.com/Wenwang/archive/2011/09/21/2184102.html) <!DOCTYPE html> <html l ...

  6. RxJava开发精要8 – 与REST无缝结合-RxJava和Retrofit

    原文出自<RxJava Essentials> 原文作者 : Ivan Morgillo 译文出自 : 开发技术前线 www.devtf.cn 转载声明: 本译文已授权开发者头条享有独家转 ...

  7. ARM学习日记

    2012-05-15 1.ARM开发板环境的搭建,nor启动,通过suppervivi,下载vivi---下载Kernel----下载文件系统,然后Nandflash启动即可. 2.在/etc/ini ...

  8. 深入Java虚拟机——类型装载、连接(转)

    来自http://hi.baidu.com/holder/item/c38abf02de14c7d31ff046e0 Java虚拟机通过装载.连接和初始化一个Java类型,使该类型可以被正在运行的Ja ...

  9. c语言库函数测试

    1.函数名: abort功  能: 异常终止一个进程用  法: void abort(void);程序例: #include <stdio.h> #include <stdlib.h ...

  10. 用Node&period;js写爬虫,撸羞羞的图片

    说到爬虫,很多人都认为是很高大上的东西.哇塞,是不是可以爬妹纸图啊,是不是可以爬小片片啊.答案就是对的.爬虫可以完成这些东西的操作.但是,作为一个正直的程序员,我们要在法律允许范围内用爬虫来为我们服务 ...