C语言一般是进入大学学的第一门计算机语言, 这里做一个简单回顾。
第一部分: C语言简介
C语言和C++不同,C语言是面向过程的语言,也就是说,他的基本组成结构是函数,关于更多面向过程和面向对象的介绍可以看我的《C++程序设计》。
推荐工具:C语言在线运行工具
推荐文档:c语言&&c++
推荐文章:http://www.open-open.com/doc/view/f411756be0444ff6a95a992ffb60e448 C/C++经典面试题
对于c,我们可以将之保存为c文件,然后按照下面的方式执行即可:
第二部分:C语言程序实例
#include <stdio.h> int main() { /* 我的第一个 C 程序 */ printf("Hello, World! \n"); return 0; }
这段代码就可以输出“Hello, world!” 了。
- 可以看到,和C++一样,它也需要引入一个文件,如C++中#include <iostream> ,而这里也是引入了一个io类似的文件。用来在编译的时候告诉编译器实际编译时要包含 stdio.h 文件。
- 并且也有一个 int main()函数,和C++十分类似,且最后返回 0,即 return 0,return 0 即表示退出程序。
- /* ... */ 用于注释说明,在实际编译时会被编译器忽略。
- printf() 用于格式化输出到屏幕。printf() 函数在 "stdio.h" 头文件中声明。 正如C++中使用cout需要引入 <iostream>是一样的。
- stdio.h(standard i/o head) 是一个头文件 (标准输入输出头文件) and #include 是一个预处理命令,用来引入头文件。 当编译器遇到 printf() 函数时,如果没有找到 stdio.h 头文件,会发生编译错误。
- 可以看到C语言这里并没有命名空间,而C++中是有的,一般具有命名空间的语言更适合编写更为复杂的函数。
别看C语言这么简单,他还是挺能做事情的,如当今最流行的 Linux 操作系统和 RDBMS(Relational Database Management System:关系数据库管理系统) MySQL 都是使用 C 语言编写的。
不仅如此,操作系统、语言编译器、汇编器、文本编辑器、打印机、网络驱动器、现代程序、数据库、语言解释器、实体工具等都是使用C语言编写的,所以作为程序员是没有理由不学习C语言的!
第三部分:C基本语法
C语言中的五个令牌是:分号、注释、标识符、关键字和空格。
- 分号: 和js及C++相同,C语言中的分号也代表一距代码的结束。
- 注释: 即/*这是注释*/ , 和js及C++相同,我们都可以使用类似的注释方式。
- 标识符:C 标识符是用来标识变量、函数,或任何其他用户自定义项目的名称。一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。关键: 在C语言中,标识符是指用户自定义的。 而js却不是。另外,C语言中的标识符和C++中类似,都是允许字母或下划线开头,后接字母、数字、下划线,同样不支持$。 在js中,是支持$的。
- 关键字: 在C语言中,关键字是指C语言自身定义的,如 break 、if、else等等。
- 空格:
第四部分: 数据类型
在C语言中的数据类型指的是用来描述不同类型的变量或函数。
整数类型:
- char 字符
- unsigned char 无符号字符
- signed char 有符号字符
- int 整型
- unsigned int 无符号整型
- short 短整型
- unsigned short 无符号短整型
- long 长整型
- unsigned long 无符号长整形
我们可以看到在c中我们可以使用unsigned和signed对int、short和long进行描述。
为了得到他们所占的字节数,和C++一样,我们可以使用sizeof()方法来查看:
#include <stdio.h> int main() { printf("内存:%lu", sizeof(int)); }
最后会输出: 内存:4 (可以看到,在printf的第一个参数中我们传递了一个%lu,这是为了说明sizeof(int)的类型,后面会重点来讲,这个和js中console.log是类似的,可以进入www.eeyes.net然后按下F12查看, 那里我是用的是%c),,如下:
浮点类型:
- float 浮点类型
- double 双精度浮点类型
- long double 长双精度浮点类型
void类型
void类型表示没有可用的值,通常用在下面的三种情况:
第五部分: 变量
这一部分是我们用的比较多的,和C++相比,基本的变量类型中,比C++少了两个,一个是bool(布尔型),另一个是wchar_t。
变量定义和C++也是一样的,如下所示,下面的都是合法的:
int a; char b; float c, d;
即每一个定义是一个语句,我们还可以同时定义多个变量,使用 逗号 分割即可。
另外,我们也可以在定义变量的时候同时初始化,如下所示:
int a = 8; char b = 'c'; float c = 5, d = 10;
关于变量的声明有一个地方需要注意,这个C++是相同的,即
- 需要建立空间的变量声明 如int a; 在声明a是一个整型的时候就建立了存储空间。
- 不需要建立空间的变量声明 , 即使用extern关键字声明变量名,而不定义它,如 extern int a; 这个语句表示声明变量a,但是不定义它。
注意: 在c语言中严格区分了 声明 和 定义 。 声明表示我有这个变量,不一定是刚刚存在的, 而定义表示我是刚刚出生的,刚刚存在的,需要占据存储空间的。
extern int i; //声明,不是定义 int i; //声明,也是定义
字符常量
字符常量可以是一个普通的字符(例如 'x')、一个转义序列(例如 '\t'),或一个通用的字符(例如 '\u02C0')。
在 C 中,有一些特定的字符,当它们前面有反斜杠(一定要是反斜杠)时,它们就具有特殊的含义,被用来表示如换行符(\n)或制表符(\t)等。下表列出了一些这样的转义序列码:
其中,b表示back - 回退, n表示newline-换行, r表示carriage return - 回车, t表示table- 制表, v表示verticle table -垂直制表。
如下所示:
#include <stdio.h> int main() { printf("zzw \n hett \t shgua \t shaga \n over"); return 0; }
最终的输出如下:
zzw
hett shgua shaga
over
定义常量
在C语言中定义常量和C++中定义常量完全一致,即#define 和 const两种方法:
#include <stdio.h> #define GOD 888 int main() { const int a = 10; printf("define %d \n const %d",GOD,a); }
可以看出,其中使用#define定义常量的时候不需要写变量类型,且最后不能有分号, 变量名必须是大写。另外,在printf中,我们必须使用%d对应着输出。
第六部分:C函数
多行代码完成一个功能,封装起来之后就是一个函数,C语言亦是如此,且至少要有一个主函数main()。
函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。 (注意:函数声明和函数定义是不同的,其中,函数声明是指:告诉编译器,在这个程序中有这个函数。 而函数定义是,直接定义了这个函数,即其内容是什么)
可以看出,和C++并没有什么不同,另外,注意: 如果这个函数就只是干某一件事儿,最后什么都不需要返回,那么这个函数的类型就是void。
C语言中传递参数有两种方式,和C++类似,一种就是传值调用,另一种就是引用调用。 前者在函数体内不会改变函数外的值,而后者在函数内被改变的时候,函数外的值也会跟着改变。
在C语言中,变量同样具有局部变量和全局变量,指的注意的是:C和C++没有像js一样的变量提升,即使用前必须先声明。
第七部分: C数组
c语言中的数组和C++中的数组是非常类似的,定义方式如下:
int a[100] = {20, 50, 30, 8};
第八部分: 指针
在C中的指针和C++中的指针几乎是一样的,值得注意的是,C语言中说了这么一句话:
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。如下所示:
int *ptr = NULL;
第九部分: 字符串
和C++一样,定义字符串的方式有以下两种:
char str[10] = {'d','a','f','e','\0'}; char str[] = "dafe";
即在C语言中,实用\0表示终止一个字符串。
另外,C语言中有内置的方法用来操作字符串,但在实用之前需要引入文件,即#include <string.h> 这样才能使用其中的方法。
第十部分: 结构体
C语言中的结构体和C++中一样,都是用于存储不同类型的数据的。如下所示:
struct Books { char title[50]; char author[50]; char subject[100]; int book_id; };
然后我们可以使用Books.title、Books.author来调用其中的数据。
其中Books是结构体名。
另外,结构体还可以作为参数传递到函数中。
如果我们的代码如下:
struct Books { char title[50]; char author[50]; char subject[100]; int book_id; }a;
就代表在定义结构体 Books的时候同时定义了结构体变量a。
另外:
struct { char title[50]; char author[50]; char subject[100]; int book_id; }a,b,c ;
假设除了结构体变量abc之外我们不再需要其他更多的结构体变量,我们可以不写结构体名。。
注意:在C语言中的结构体只有成员变量,在C++中的结构体不仅仅有成员变量还有成员函数,另外,在C和C++中,成员变量和成员函数都是public(虽然c没有这个概念,但是可以这么理解..)
如下:
struct student { int Chinese; int Math; int total; }; struct student a; //定义结构体a a.Math = 100; a.Chinese = 100; a.total = 200; //写 printf("%d %d %d",a.Chinese, a.Math, a.total);
使用C语言就可以定义这么一个结构体,即使用关键字 struct 然后定义结构体的名称,其实和类的概念差不多,然后使用 struct student a; 来实例化这个结构体。 我们就可以来写和读了。
其中的a更为专业的叫做结构体变量。
在C++中我们可以这么来写:
#include <iostream> using namespace std; int main() { struct student { int Chinese; int Math; int total; int add() { return total+Math;} }; struct student a; //定义结构体a a.Math = 100; a.Chinese = 100; a.total = 200; //写 cout << a.Chinese <<endl<< a.Math <<endl<< a.total<<endl<< a.add(); return 0; }
可以看到,C++来C语言的结构体是类似的, 只是在C++中的结构体中可以写方法,并且可以直接访问其中的属性。
第十一部分: 共用体
共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。
共用体和结构体是十分相似的,唯一区别就是结构体每次都只能创建一个,而共用体相当于创建了一个新的类型,可以使用它创建任意多个实例。
第十二部分: 输入输出
在C中我们使用scanf()函数输入,使用printf()函数输出。
看下面的例子:
#include <stdio.h> int main() { int testInteger = 5; printf("Number = %d", testInteger); return 0; }
即这里输出使用的就是printf()函数。 printf()函数接受两个参数,第一个参数是要输出的提示文字(Number = )以及要输出的变量的类型(即%d表示整数)。
即:在 printf() 函数的引号中使用 "%d" (整型) 来匹配整型变量 testInteger 并输出到屏幕。
在看下面的这里例子:
#include <stdio.h> int main() { float f; printf("Enter a number: "); // %f 匹配浮点型数据 scanf("%f",&f); printf("Value = %f", f); return 0; }
在这个例子中可以发现,scanf()的第一个参数是%f,它的作用是:匹配浮点型数据. 而第二个参数是&f,这是因为这里使用的是引用传递,我们希望通过引用传递在函数内改变值同时也改变穿进去的对应地外面的那个变量。
%s、%d、%c、%f 、%ld 分别用于匹配 字符串 整数 字符 浮点数 十进制长整型
%x 可以得到一个变量的地址。
%lu 可以用于输出 类型的长度,如 int类型为4.
举例如下:
#include <stdio.h> int main( ) { int c = 15; char a = 'z'; char b[] = "hetingting"; float m = 3.14159; long int p = 4563; printf("%d\n%c\n%s\n%f\n%ld",c,a,b,m,p); return 0; }
最终的输出结果如下所示:
15 z hetingting 3.141590 4563
getchar()和putchar()函数
int getchar(void) 函数从屏幕读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。您可以在循环内使用这个方法,以便从屏幕上读取多个字符。
int putchar(int c) 函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符。您可以在循环内使用这个方法,以便在屏幕上输出多个字符。
举例如下:
#include <stdio.h> int main( ) { int c; printf( "Enter a value :"); c = getchar( ); printf( "\nYou entered: "); putchar( c ); printf( "\n"); return 0; }
gets和puts函数
#include <stdio.h> int main( ) { char str[100]; printf( "Enter a value :"); gets( str ); printf( "\nYou entered: "); puts( str ); return 0; }
这两个函数会读取一整行,这是和getchar以及putchar不同的地方。
最后再看一个例子:
#include <stdio.h> int main() { int c = 8/3; printf("结果是%d",c);/*2*/ return 0; }
即c使int类型,那么最后得到的也是int类型,且和js中的Math.floor()作用相同。
就总结到这吧,还有强制类型转换等需要继续完善,后续有时间继续。
补充知识
1. 逗号表达式
问题: 若a是int类型的值,则表达式(a=4*5,a*2),a+6的值是多少。
第一步,从左往右求解, 即 a = 20 然后a * 2 = 40 所以(a = 4*5, a*2)最后的取值为其中的最后一个表示式的值40,但是整体求值并没有用。接着20 + 6 =26,故26作为这整个式子的值。最终答案为 26。
2. C语言中类型转化问题
若b是int型变量,那么 b = 25/3%3的值最后是多少。 ------ 因为/和%的优先级相同,所以从左往右计算,25/3为8.3333,故余数为2.3333,但是b为int类型,所以最后要转化为2.
另外:int a = 8/2.1;
printf("%d",a);
最后的结果是 3,其真实的为3.8,但是int采取的是向下取整。
3.
第十三部分:一些例题
1.
#include <stdio.h> #include <string.h> int main() { char color[] = "blue1"; int len = strlen(color); printf("%d\n", len); printf("%ld\n", sizeof(color)); return 0; }
定义一个字符串,输出其长度,使用sizeof输出其所占内存,因为长度为6,所以要用ld,即长整型, 否则会有warn。
因为除了blue1这五个字符意外,还有一个'\0'表示结束。
我们还可以使用 char red[] = {'b','l','u','e','1','\0'}; 最后和color是一样的。
#include <stdio.h> #include <string.h> int main() { char df[5] = "fd"; printf("%ld\n", sizeof(df)); char dd[] = "fd"; printf("%ld", sizeof(dd)); }
得到的分别是 5 和 3,因为对于前者我们分配的内存长度就是5. 而后者我们没有分配,而是根据实际的长度,实际长度即fd再加上结束符即可。 注意:输出一般使用ld(长整型)
#include <stdio.h> #include <string.h> int main() { printf("%lu", sizeof(double)); printf("%lu", sizeof(char)); printf("%lu", sizeof(int)); printf("%lu", sizeof(long int)); printf("%lu", sizeof(float)); }
输入 8 1 4 8 4 。
注意使用lu
2.取得地址,使用 %x 输出 , &一个变量。
char od[5] = "dfas"; printf("%x\n", &od);
可以输出od的地址为:f807e070
3.
#include <stdio.h> #include <string.h> int main() { char acColor[] = "blue1"; printf("%ld\n", strlen(acColor)); printf("%ld\n", sizeof(acColor)); }
可以得到这个字符串的长度为5,即blue1。 而字符串的内存大小是 6,因为内存中还多了一个\0。
4. 在C语言中 !的优先级高于 % 高于 >> 高于 ==
5. 若a,b,c都是int,初始值都是0,那么 a=(b=4)+(c=2)的值是6,因为()的优先级最高, 先完成赋值运算,然后再相加。
6. 一个tt.exe文件,在输入了 tt 12 345 678 之后会输出什么?
int main(int argc, char**argv) { int n = 0,i; for (i = 1; i < argc;i++) n = n * 10 + *argv[i] - '0'; printf("%d\n",n); return 0; }
上面的代码再在命令行中输入 会输出什么?
说明:
当一个C的源程序经过编译、链接后,会生成扩展名为.EXE的可执行文件(对windows操作系统而言),这是可以在操作系统下直接运行的文件,换句话说,就是由系统来启动运行的。对main()函数既然不能由其它函数调用和传递参数,就只能由系统在启动运行时传递参数了。在操作系统环境下,一条完整的运行命令应包括两部分:命令与相应的参数。其格式为:命令 参数1 参数2.... 参数nt;此格式也称为命令行。命令行中的命令就是可执行文件的文件名,其后所跟参数需用空格分隔,并为对命令的进一步补充,也即是传递给main()函数的参数。命令行与main()函数的参数存在如下的关系:设命令行为:program str1 str2 str3 str4 str5 (例如在cmd下, 复制文件为 COPY 源文件目录 目标文件目录)其中program为文件名,也就是一个由program.c经编译、链接后生成的可执行文件program.exe,其后各跟5个参数。对main()函数来说,它的参数argc记录了命令行中命令与参数的个数,共6个,指针数组的大小由参数argc的值决定,即为char*argv[6。数组的各指针分别指向一个字符串。应当引起注意的是接收到的指针数组的各指针是从命令行的开始接收的,首先接收到的是命令,其后才是参数。
char **argv是一个指向字符型的指针数组的指针,首先它是一个指针,这个指针是指向数组的,这个数组里面的成员也指针,成员指针是指向字符型数组的。
所以由上面的概念我们知道,argc是输入命令行的命令数: 即为4.
而 char**argv可以知道一般的格式是 char*表达后面的东西是一个指针,于是 *argv是一个指针。 而*argv又是一个指针数组。其中数组的每一个指针指向一个字符串的首字符,故这里指针数组额长度应该为4,分别是4个字符串。
那么 i = 1 时,n = 0 + '1' - '0';
i = 2时, n = 1 * 10 + '3' - '0' = 13 ; (注意: n是int类型,所以最后是13)
i= 3 时, n = 13*10 + 6 - '0 ' = 136;
所以最终的输出结果是136。
7. int x[] = {1,2,3,4,5,6}; *p = x; 则值为3的表达式是: p+=2, *p++
注意: int *p = x;是声明并定义p指针,并指向x的首元素地址, p+=2会指向第三个, 而 *p++是先指向那个元素,然后再自增,不会有影响。
看下面的C++的实例:
#include <iostream> using namespace std; int main() { int a[] = {5, 6, 5, 8, 4, 2}; int *p = a; cout << p << endl<< (p+3) << endl << &a; return 0; }
最终的输出如下:
0x7fff9a9e8490 0x7fff9a9e849c 0x7fff9a9e8490
可以发现 p 和 &a的值是一样的,因为指针的值就是他所指向的变量的地址(第一个元素的地址),而我通过p + 3 可以让指针后移3位,所以地址也就向后移动了三位。
8. 在什么情况下适合采用inline定义内联函数 --- 这一般是C++中的概念。 在函数代码少、频繁调用的时候采用。 因为内联函数通常是用来消除函数调用时的时间开销。它通常用于频繁执行的函数,一个小内存空间的函数非常受益。
9. 输入如下:
#include <iostream> #include <cstring> using namespace std; int main() { char ch[] = "abc\0def"; char *p = ch; cout << *p << endl << *p+4 << endl << (char)(*p+4) << endl << ch << endl << strlen(ch); }
输出如下:
a 101 e abc 3
即 char *p = ch; 就是将指针p指向ch数组(的首地址)。 另外 *p 理解为p所指向的, 由于*的运算符很高(一般一元操作符要高于二元的),所以*p指向了a,再+4就会被转化成数字(隐式类型转换), 最后我们用char来强制类型转化。
最后一步我们输出ch的长度,发现是3,说明字符串遇到了 \0 就自动终止了,后面的就不要了。
隐式类型转换的基本规则: 由低精度->高精度,举例如下:
char c = 'a'; cout << c + 2 <<endl;
输出为 97, 因为整型int的精度更高,所以会向int转化,
cout <<sizeof(int) <<endl<< sizeof(char);
最终会输出4和1,所以可以转化成int,但是如果我们希望将数字转化为字符,我们可以使用强制类型转换,如下:
char c = 'a'; cout << char(c + 2);
最终会得到c。
10. 请问下面的程序输出的结果是:
unsigned short a = 65536; int b; printf("%d\n", b=a); return 0;
最终输出的是0。
printf("%lu",sizeof(unsigned short)); printf("%lu",sizeof(unsigned short));
最终的输出结果是 2 , 即无符号短整型的内存为2个字节(注int为4,long为8,char为1,double为8,float为4), 2个字节就是 16bit,那么他能表示的最大的数就是2的16次方减1= 65535,也就是16个1,而这里是65536,所以加1,就全部成了0, 故a就是0, b也是0了。
注意:16位的最大数是 2 的 16次方减1. 正如3位即111,最大为7,即2的三次方-1是一个道理。
11.
char dh[] = "hello"; char *p ; p = dh; cout << p; return 0;
上述代码实际上就等价于 : char dh[] = "hello"; char *p = dh;
13. int(-123.5)是多少?
123 在c和C++中,如果要转化成整型,就直接把小数点后面的小数去掉即可。
14 如下代码的输出结果是什么?
#include <stdio.h> void fun(int a, int& b, int* c) { a = 456; b = 567; *c = 678; } int main() { int x = 10,y = 20,z = 30; fun(x, y, &z); printf("%d,%d,%d\n",x,y,z); // 10,567,678 }
最终代码是:10, 567, 678
在c++中,支持三种传递方式,在C中应该也是的。
(1)其中x是直接传入的,说明这是值传递---即只是把x的值赋值给了a,此后两者再无关系,因此a的改变不会影响到x。
如:
int b = 20;
int a = b;
a = a + 5;
cout << a <<endl <<b;
最终的输出是 25 20。 即赋值之后a和b再无关系。
(2)而y传递的时候,使用的是引用传递,即b的改变同时也会影响着在外面的y。 引用传递可以理解为一个人起了另外一个小名,无论叫大名还是小名,都是一个人。
如:
int b = 20;
int & a = b;
a = a + 5;
cout << a <<endl <<b;
最终输出的是 25 25。 即引用传递传递的就是一个人。
说明: int &a = b; 也可以写成 int & a = b; 也可以写成 int& a = b;最后的结果都是一样的。
(3)而z传递的时候可知传进去的是地址。而c是指针,所以*c就是穿进去的z,*c的改变也会导致z的改变。
如:
int b = 20;
int *a = &b;
*a = *a + 5;
cout<<b;
最终输出的是25。
说明:之前我们使用过 char ch[] = "hello"; char *p = ch;然后输出 *p其实输出的就是ch。 而为什么这里必须要使用&b而不能直接是b呢?
因为只有char可以这样。默认情况下 char *p = ch 就是将p指向ch的首元素的地址。 但是对于int等,必须要取得地址(使用&),而指针的值就是地址,这样就匹配了。
如果希望在b不是char的情况下,这样做有效,只有可能是b也是一个指针,进而就成了指针之间的赋值。
15.下面的代码返回什么?
#include <iostream> #include <cstring> using namespace std; int main() { cout << strcmp("3.14","3.278");; return 0; }
strcmp是比较函数,它顺序对两串字符串进行比较ASCII码,如果最终前者大于后者返回正整数,等于则返回0,小于则返回负整数。 这里返回1。
16. char x[] = "abcdefg";和char y[] = {"a","b","c","d","e","f","g"};的区别是什么?
前者和后者的长度相等。但是前者比后者的内存多1(因为编译时会自动给前者后面添加一个“\0”,而后者应该是我们自己添加的,系统不会添加),如下:
char x[] = "abcdefg"; char y[] = {'a','b','c','d','e','f','g'}; cout << strlen(x) << endl << strlen(y)<< endl; cout << sizeof(x) << endl << sizeof(y) << endl;
最终的输出是 7 7 8 7。
17. 下面的结构体:
#include <iostream> #include <cstring> using namespace std; int main() { struct {short a;char b;float c; }cs; cout << sizeof(cs)<< endl << sizeof(short)<< endl <<sizeof(float)<<endl<<sizeof(char); }
最终输出多少(主要是sizeof(cs))?
答案为8。注意:虽然short为2,char为1,float为4,加在一起应该为7,但是结构体需要考虑结构体对齐的情况,而且还要看你所用的编译器平台,在vs平台下,默认对齐数为8,首先一个第一个变量在0偏移处。 短整型占两个字节,现在就到了2偏移处,一个char占了一个字节,2是1的倍数,直接加在后面,现在是3偏移处,而float是4个字节,不是3的倍数,需要再开辟一个字节,到4偏移处,然后加上4,所以结构体是8个字节。
18. 在c语言中和C++语言中,%运算符的两边只能是整型,否则会报错。如下面的式子就会报错。
float a = 5.2;
cout << a%2;
fadf