点击链接加入群【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++。