点击链接加入群【C语言】:http://jq.qq.com/?_wv=1027&k=2H9sgjG


    

话说五百年前,晕,喝点小酒,说了点迷话- -

话说我感觉C语言这一块,大家只要把我们前面分析的内容掌握好,就已经足够了,还有一些小知识点,都不是什么太重要的东西啦,我会在后续的小节中想起来的时候把这些小知识点加上去。

大家要自己弄清楚的一些东西 如:

printf  sprintf  fprintf 看着很像,那么区别在哪里

还有一些内容是需要大家随着工作及不断的学习逐渐完善,比如,大家可以把一些常用的字符串函数记下来,或者直接百度C语言字符串常用函数,看看前辈们总结的一些常用的,记住,我们要的不是大家去记忆这些函数,当然常用的是一定要记住啦,但是大家要知道我们有哪些功能的字符串函数可以用,不要到时候你要取一个子串还要自己取,也就是你得知道学用字符串函数有哪些常用操作,比如取子串,比较,拷贝等等。

字符串函数搞好后,就要研究一下常用的文件操作函数啦,也就那么几个常用的,也可以直接百度,加上前面的理解,我相信很多函数都是轻而易举就能被你拿下的。


然后就要复习 好 对 地址的认识 ,对指针的理解 ,对类型的认识 ,在这里,我们再来分析一个函数:

void ( *signal( int sig, void (__cdecl *func) ( int sig )) ) ( int sig );

这个函数有点小复杂,我们一看看吧

函数名 signal

紧随其后的是函数参数,( int sig, void (__cdecl *func) ( int sig ) )

我们可以看到它有两个参数

参数1: int sig

参数2: void (__cdecl *func) ( int sig ) ;这个大家应该能看懂了吧,就是一个函数指针哦


我们来组装一下前面的内容


返回值类型 signal( int sig, void (__cdecl *func) ( int sig ))


现在就差一个返回值类型了,那么这个函数的返回值是什么呢? 如果是void 那么直接在前面加个void就行了,如果返回普通指针,那么就直接写 类型* 就可以了,

但是如果返回的是一个函数指针怎么办呢?其实这个函数返回的就是函数指针,是一个 void (*)(int sig)类型的指针,所以,当函数指针做返回值类型的时候 ,我们只要把函数名写在 函数指针里 * 的后面就行了。现在应该可以看懂了吧。我们不关心它到底有多么复杂,我们都可以慢慢的拆,那么现在按照正常顺序,你自己再理解理解,其实我们一般是不会弄出来这么复杂的函数的,而且不要忘记,我们还有个typedef功能呢,

typedef void (* signalRet)(int sig);

typedef void (__cdecl *signalPara) ( int sig );

signalRet signal(int sig,signalPara); 这样子再看是不是简单明了呢。


C中还有一个小问题,就是extern 的作用,我们之前也说过了,用于声明变量是在别的文件中定义的。

那么现在有个问题,就是变量如果是数组或指针,在声明的时候应该怎么声明。


实例1:

b.cpp中

int val = 12;


a.cpp中

#include <stdio.h>

extern int val;  //声明一下val并不是定义在a.cpp中,而是定义在其它的源文件中

int main()

{

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

return 0;

}

如果我们把extern 去掉,再组建程序,会收到类似如下的提示

Linking...

a.obj : error LNK2005: "int val" (?val@@3HA) already defined in b.obj

注意:这个是链接错误,编译器编译 b.cpp没有问题,正常生成b.obj,编译a.cpp也没有问题,正常生成a.obj,问题出现在将a.obj和b.ojb链接的时候,由于a.obj和b.obj中都有一个定义的变量val,所以就出现了问题了,这里有一点要注意,我们的项目中一直都是创建的.cpp文件,从没有建过.c文件,如果这两个文件都是.c的话,那么这个问题就不存在了,.c和.cpp是有那么一点点区别的,不过我们一般不用.c啦。


实例2:

b.cpp

int val =12;

int arr[3]={1,2,3};


a.cpp

#include <stdio.h>

extern int val;

extern int arr[];  //数组的声明是这个样子的,也可以是 extern int arr[3] ,3也可以是其它数值

int main()

{

printf("%d\n",sizeof arr);//如果声明形式为 arr[],则这句话是编译不过的,如果是arr[3],则可以计算

return 0;

}

还记得上一小节中sizeof 的使用介绍吗,不能用于计算 外部数组的 大小,所以正确的声明方法应该是  extern int arr[]; 而不是 extern int arr[3];虽然它也可以。

而指针呢,也跟案例1中的一样,就是用 extern 类型 变量名,声明一下就行啦。


我刚才又想到两个没有讲过的内容,一个是const 另一个是 函数调用约定

const 可以用来修饰变量,被它修饰的变量的值是不能改变的,

const int val =12;

val = 13;

上面的代码是不能通过编译的,其实另大家感觉麻烦的是它与指针混合在一起的时候

int val = 12;

int * p = &val;

如果要给第二话加const,有4个位置可以添加

A: const int * p1 = &val;

B: int const * p2 = &val;

C: int * const p3 = &val;

D: int * p4 const = &val;

我们逐一测试,会发现D是不可以通过编译的,而其它的都可以,所以说 const 可以添加在 最前面,*号的 左边和右边,OK了,我们已经掌握了位置了

那么我们来一一测试效果

注:const在与指针变量一起的时候,有两个功能,一个就是修饰指针变量(p1,p2,p3),也就是p1,p2,p3的值是不能修改的,另一个就是修饰指针指向的数据(val),也就是val的值是不能通过指向它的指针来改变的。

测试:

#include <stdio.h>

int main()

{

int val =12;

int test = 13;

const int * p1 = &val;

p1 = &test;

//*p1 = 13;这个不行


int const * p2 = &val;

p2 = &test;

//*p2 = 13;这个不行


int * const p3 = &val;

//p3 = &test;这个不行

*p3 = 13;

//int * p4 const = &val;//这个不行

return 0;

}

好,我们发现,

前两个操作效果是一样的,而第三个的效果是相反的,

前两个的const在*号的左边,而第三个的const在*号的右边

就这些就已经足够啦,我们发现,

当const在*号的左边时,const修饰的是指针指向的数据,此时不能通过这个指针修改其指向的数据的值,但是值本身可以修改

当const在*号的右边时,const修饰的是指针变量本身,此时指针变量本身的值是不能够修改的


好了,那么如果我们要修饰两个值呢,那么就需要加两个const啦,*号左边一个,*号右边一个

const int * const p = &val;

好,我们来看一下它有什么用,还记得我们在哪里见过它吗

char *strcpy( char *strDestination, const char *strSource );

这个const在*的号左边,也就是我们在函数的内部不能通过 *strSource = xx ;来对它进行赋值,想想也是这样,我们不能在拷贝数据的时候将原数据给修改了,所以要加个const关键字,这样大家就理解了吧。

至于修饰二级指针,本人以前是做过详细分析的,不过在实际工作中基本上不使用,而且C++中也不建议使用指针,而应该更多的使用引用,所以,就不带着大家进行详细分析啦,如果有兴趣,可以按照我们前面的测试方式来进行测试。



这上大问题可以画一个句号啦。我们再来看看函数调用约定,

我们在第六小节中,讨论了函数调用的过程,知道其中有 将参数按顺序压入栈中的操作,函数退出的时候有平衡堆栈的操作,而函数的调用约定就是来规定这些操作的。

我们没有必要去记忆这些东西,只需要知道是怎么回事就OK啦,详细的大家可以自己调试一下,看一下详细过程。

我们可以在函数名前面加上函数调用约定 如

#include <stdio.h>

int __cdecl add(int a,int b)

{

return a+b;

}

int __stdcall sub(int a,int b)

{

return a-b;

}

int main()

{

int vala = add(1,2);

int valb = sub(2,1);

return 0;

}

也就是大家要知道的一件事情,它们对函数调用过程中参数压栈顺序有规定,比如是从左向右依次压栈呢 还是从右向左依次压栈呢,还有就是函数返回前,由参数引起的堆栈不平衡由谁负责平衡,是由函数调用者平衡还是由被调函数一平衡。

我们以后还会看到一些宏,比如

#define PASCAL   __pascal

#else

#define PASCAL

#endif

#elif (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED)

#define CALLBACK  __stdcall

#define WINAPI   __stdcall

#define WINAPIV   __cdecl

#define APIENTRY  WINAPI

#define APIPRIVATE __stdcall

#define PASCAL   __stdcall

我们会看到以后遇到 WINAPI  CALLBACK 等宏的时候 你就应该知道它是什么意思了吧。转到定义功能你不会又忘记了吧。


好,C语言暂时就告一段落,下一部分,我们将进入到C++。