C语言总结

学习C语言很久了,感觉总是学习的不够系统,所以就在这总结一下,简单的部分在此仅列出提纲,然后我感觉难的部分就稍微写一下,然后后续出一下C++的部分,就开始算法笔记的书写。

数据的表示

二进制,十进制,十六进制相互转化

ASCII码

C语言提供了ASCII码来表示字符,即使用 “\“ 和一个通过八进制和十六进制的字符来进行表示。

1
2
\ddd		//八进制数表示形式
\xdd //十六进制表示形式

数据类型

有符号整数:int short long long long 现在大多数情况下intlong占的存储单元数相同,表示的数据范围(-231 ~ 231 - 1)也就相同了,在使用之前可以使用sizeof()函数进行查看。

无符号整数:unsigned int unsigned short unsigned long unsigned long long 其中 unsigned int的取值范围为 0 ~ 232 - 1。

浮点类型:float double long double 这里主要强调还有最后一项long double。必要的时候可以用。

字符类型:char

运算符和表达式

自动类型转换

  • 若数据的类型不同,必须先转换为同一类型,然后再进行运算(计算或比较)。
  • 转换方向:由存储单元的向存储单元的转,由精度的向精度的方向转。
  • 浮点运算一律转为double类型再进行计算。
  • 字符型的char参与运算的时候,先转换成int,然后进行计算。
  • 在赋值语句中,赋值语句=两边的数据类型不同时,一定是=右值的数据类型不同转换为左值的数据类型(注意精度损失)。

强制类型转换

1
(类型说明符)(表达式)

说明:强制类型转换只是暂时性的转换,并不会改变变量的类型,变量的类型在变量首次出现的时候就已经被决定了。

取余%

使用规则:余数的符号与被除数一致

1
2
3
-3 % 9  = -3;
12 % -5 = 2;
-4 % -8 = -4;

自增自减运算符

形式 含义 使用举例
++i 先自增1再参与其他运算 j=++i, i=6, j=6
–i 先自减1再参与其他运算 j=–i, i=4, j=4
i++ 先参与运算,再自增1 j=i++, i=6, j=5
i– 先参与运算,再自减1 j=i–, i=4, j=5

注:初始值 i = 5。自增自减运算符出现的意义是为了提高效率,不需要进行完整的一次加法操作。

语句

格式化输入scanf()

1

对于用户的输入,计算机把它看作一串。scanf()按照格式字符串指定的格式从输入串中解析数据,然后送到输入项的变量中。但要注意:当用户输入的数据大于输入项时,多余的数据仍然留在输入串中,而没有作废,等待下一个输入操作时被解析,比如:

1
2
3
4
5
int a,b,c;
scanf("%d %d",&a,&b); //如果在此时输入三个整数
printf("%d %d",a,b);
scanf("%d",&c); //将直接跳过此条语句,因为输入串中已经有足够的数据
printf("%d",c); //效果大家可以自行试一下

2 跳过某个输入数据

%和格式字符之间加入*号,程序会跳过相应的输入数据,但是只是不将数据装入对应的变量,依然还是会消耗掉一个对应位置的数据。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>

int main()
{
int a=0,b=0,c=0;
scanf("%d %*d",&a,&b); // have a try!
printf("%d %d",a,b);
scanf("%d",&c);
printf("%d",c);
return 0;
}

getchar()

它的功能是从键盘中读取一个字符,然后赋给变量。使用方法如下:

1
2
char ch1 = getchar();
char ch2 = getch();

注意:在输入的字符间不要增加任何间隔符,因为间隔符也是字符,会被保存到变量中。附一个:getchar()和getch()的区别

gets()

它的功能是从键盘中读取一串字符串,然后赋给变量。使用方法如下:

1
gets(ch);

gets() 以回车为字符串输入结束标志,而scanf() 以间隔符(空格,tab,回车)作为结束标志,使用时注意区分。

格式化输出printf()

数据对齐:指定输出数据的宽度(在%后加数字以表示输出位数),可以方便的进行数据的对齐操作!

返回值:printf() 的返回值为int类型,大小为本次调用时输出的字符的个数(包括回车等控制字符)。

其他输出方式:putchar(ch) puts(ch)

常见语句

赋值语句、空语句、复合语句(语句块)

选择语句

1 逻辑运算符优先级:”!”优先级最高 ,”&&”次之,”||”最末

2 逻辑运算符短路问题:即表达式运算完一部分后就不再运算的现象。

1
12>3 || 5<0  //这里在进行完前面的比较后,后面的就不运算了

大家看起来现在感觉很“正常”,但是在某些特定情况下还是有影响的,会出现一些特定的bug,比如

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>

int main()
{
int i = 4 ,j = 6;
{
i++ || j-- ; // i++ 后值为真,然后就会跳过后续j--,继续向下执行
}
printf("%d %d",i,j); //最后打印结果为 5 6 而不是 5 5
return 0;
}

3 switch语句无法处理对浮点数的判断。

循环语句

1 语句类型for , while ,do-while

2 跳出循环continue , break, goto(大多数编译器下以不可用)。

变量作用域

局部变量、全局变量。注意:1)不同语句块内的局部变量可以重名,互不影响。2)全局变量和局部变量可以重名,在局部变量其作用的范围内,全局变量不起作用

函数

基础知识我就不赘述了,大家可以点击这里或者自行百度学习。

静态变量

这种变量即使被定义在函数内部,它也不会随着函数的返回而消失,它直到程序终止才消失。而且初始化只在第一次调用该函数的时候调用。

1
static 类型 变量名;

与全局变量的区别:静态变量如果被定义在一个函数内部,那么对于外部来说,这个静态变量是不可见的,也就是说静态变量只能够在函数内部进行操作!

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>
void try(){
static int a = 10; //1
printf("%d ",a++);
}
int main()
{
try(); //只有这一次会调用 标识1 处的语句
try(); try(); try(); try();
return 0;
}

函数声明

函数声明时,对于小括号内,数据类型必须有,参数可以不写或者任意写,因为这里的目的是提前向编译器说明:即将要使用到的函数会在使用后定义。这里仅需使编译器知道函数返回值的类型、函数参数的个数、每个参数的类型。

1
函数返回值类型 函数名(类型1,类型2,...);  //注意写分号

函数中有函数

函数嵌套、函数自身调用(递归):汉诺塔问题

库函数

若使用库函数,必须包含相应库函数的头文件。格式如下:

1
#include <xxx.h>

#写在最前面,后面跟着include,然后是一个尖括号<和文件名以及一个尖括号>,各组成部分之间是否是用空格都可以。

头文件只是对库函数进行了声明,定义库函数的代码早已经被编译成目标代码.o文件,然后和源程序编译而成的目标代码一同链接成可执行程序。

另外,自己定义的在其他文件中的函数都可以通过”文件包含“引入到当前的代码中

1
#include "xxx.h"

上述两种方法的不同是由编译器搜索文件的方式决定的。< >编译器会从include文件夹去查找相应的头文件;" "编译器会从当前项目所在的目录中查找相应头文件。所以具体使用那种方式是由头文件所在目录决定的。

C语言的函数库提供了数百个函数库,不同种类的函数分别定义在了不同的文件中,为了使用特定的函数,需要包含库函数相应的头文件。

数组

数组详细讲解请点这里,本文仅给出一些大家要注意的地方。

数组定义

1
数据类型 数组名[整型变量表达式],...   ;

注意:中括号中可以是常数表达式,不一定只是常数值。但是不可以是变量,即使是有常数值的变量。

1
2
int a = 10;
char b[a]; //XXX 这种方式就是错误的

初始化数组

1
数据类型 数组名[整型常量表达式] = {数据1,数据2,数据3,...};

若数据不够,则给数组的前面N个数进行赋值,后面的都为0,对于字符类型,默认赋值\0;若数据多于定义的数组个数,编译器会报错。

1
2
3
int num[] = {1,2,4,5,6};
int num[5] = {1,2,4,5,6}; //上面两行代码等价
int num[]; // XXX 错误的定义

冒泡排序

冒泡排序是一个重要且简单的一种排序方法,所以我们在这就简单的说一下。

题目:数组中现在有五个数据:23 57 35 63 24,请按照从小到大的顺序为数组中的数据排序,最后输出数组中排好的数据。

分析:冒泡排序法是通过依次比较相邻的两个数,来达到将大的数放置在后面的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num[] = {23,57,35,63,24};
int i=0,j=0,jj = 0;
for(j=jj;j<5;j++){
for(i=0;i<4;i++){
if(num[i]>num[i+1]){
int temp = num[i];
num[i] = num[i+1];
num[i+1] = temp;
}
}
jj++; //就可以在每一次比较可以少比较一个
}
for(i=0;i<5;i++){
printf("%d ",num[i]);
}
return 0;
}

二维数组

二维数组不可以省略第二个下标,任何情况下都不可以省略。除此之外,还有多维数组,不常用。

指针

定义方式

1
2
数据类型* 变量名
数据类型 *变量名

&为“取地址运算符”,用于取得变量在存储空间的地址。*取数值,随便给个代码吧。

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num = 9;
int *p = &num;
printf("%d",*p); //
return 0;
}

指向数组的指针

对于一维数组来说,数组名就是数组的第一个数据的地址,而且数组中的数据是被连续的存放在存储空间当中的,所以指针可以直接赋予一维数组的数组名,但是二维数组不可以这么做。正确做法如下:

1
2
3
4
5
6
7
//一维数组
int num[5] = {1,2,3,4,5};
int *p = num;
int *q = &num[0]; //上面两种做法作用相同
//二维数组
int hy[3][2] = {{1,2},{3,4},{5,6}};
int *h = &hy[0][0];

指针在函数中的用法

指针作为函数参数

将参数用指针的形式传入函数,因为是在原地址上修改数据,所以就算是修改实参,而不是形参。

指针作为函数返回值

定义函数时用这种方式:int * ppp(){ }。返回值至于加&符号进行取地址。

指向函数的指针

函数也是一种数据类型,也是存在存储地址的,指针可以指向任何存在地址。定义方式:

1
数据类型 (*指针变量名)(参数类型1,参数类型2,...)

为此类型指针赋予函数名:

1
指向函数的指针变量的名称 = 函数名   //不要带括号

下面给出一个代码展示一下用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
int max_num(int a,int b){
if(a>b)
return a;
return b;
}
int min_num(int a,int b){
if(a>b)
return b;
return a;
}
int main()
{
int (*p)(int,int);
p = max_num;
printf("%d\n",(*p)(1,2)); //输出2
p = min_num;
printf("%d\n",(*p)(1,2)); //输出1,不信大家可以复制试一下
return 0;
}

二级指针

1
2
3
4
5
数据类型 ** 指针变量名
//用于指向指针的地址
int num = 6
int *p = &num;
int **q = &p;

这个可以用于二维数组,一级指针也就对应一维数组,具体用法请自请百度,本文仅给出这个知识点(告诉你有这个东西)。

结构struct

构造结构

结构中的成员变量可以是任何类型,包括指针,结构体等一些较为抽象的类型。

1
2
3
4
5
6
struct 结构类型名
{
数据类型1 成员变量名1;
数据类型2 成员变量名2;
...;
}; //最后这个分号一定要有!!!

定义结构变量

方式一:

1
struct 结构类型名 结构变量名;

方式二:

1
2
3
4
5
6
struct 结构类型名
{
数据类型1 成员变量名1;
数据类型2 成员变量名2;
...;
}结构变量名; //最后这个分号之 前 写一个结构变量名

结构变量赋值

形式一(整体赋值):

1
struct 结构类型名 结构变量名 = {数据1,数据2,...};

注意:

  • 每个数据使用英文逗号”,”进行分隔。
  • 数据应该依次对应于结构中的成员变量,数据的类型应该和结构成员变量一致。
  • 不可以跳过前面的结构变量为后面的结构变量赋值,若只给前面的结构变量赋值,后面的结构变量会由系统默认赋值(默整型变量默认为0,字符型为/0)。

形式二(单个赋值):这个很简单就不说了,不会请百度。

使用typedef简化结构类型名

使用形式如下:

1
2
3
typedef 已定义的类型名 新的类型名;
typedef struct student_info stud_info;
typedef double dou; //这样在下面的程序中就可以用dou来代替double
  • typedef只是为类型定义别名,他本身不是一种数据类型,所以typedef无法定义变量。
  • typedef只能为已存在的类型定义别名,不能自主创造新的类型。

结构的使用

  • 指针作为结构的成员
  • 指向结构的指针
  • 数组作为结构成员变量
  • 保存结构的数组
  • 结构作为函数参数(注意是否需要传入地址,形参实参问题)
  • 结构作为函数返回值

    联合union

联合类型是一种数据类型,它与结构类型有许多相似的地方:使用这种类型前,需要使用其他类型构造这种类型,然后才能定义联合类型的变量,最后此变量才可以指代数据。

构造联合类型

1
2
3
4
5
6
union 联合类型名
{
数据类型1 成员名1;
数据类型2 成员名2;
...,
}; //再强调一遍分号!!!

定义联合类型的变量

和结构体类型相同,有两种方式。

1
union 联合类型名 联合变量名;
1
2
3
4
5
6
union 联合类型名
{
数据类型1 成员名1;
数据类型2 成员名2;
...,
}联合变量名;

引用联合类型成员

1
联合变量名.成员变量名;

联合类型初始化

赋值就不单独讲了,很简单,和结构体相同。这里重点说一下初始化的问题。由于联合类型的所有成员变量都指代相同数据类型的数据,所以只对其中一个成员初始化就可以了。

1
2
3
4
5
6
7
8
union try
{
int a;
double b;
char c;
}un1 = {10};
//或
union try un1 = {10};

联合类型变量的特殊性

  • 因为所有成员均指代同一数据,所以后写入的数据会将前面写入的数据覆盖。
  • 为了正确读取到数据,应该使用最后赋值的成员变量读取数据。

联合类型变量的简单使用

  • 使用于函数的联合类型变量

    • 函数的参数是联合类型的变量(和结构体用法类似)。

    • 函数的返回联合类型的值

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      #include <stdio.h>
      union un
      {
      char c;
      int i;
      };
      void print_union(union un);
      union un return_union();
      int main(){
      union un u1 = {10};
      u1.c = 'p';
      printf_union(u1);
      printf("\n");
      u1 = return_union();
      printf_union(u1);
      printf("\n");
      return 0;
      }
      void printf_union(union un u) //这也是形参!
      {
      printf("char:%c int:%d",u.c,u.i);
      }
      union un return_union()
      {
      union un u2;
      u2.i = 120;
      return u2;
      }

      结果截图如下:

  • 指向联合类型变量的指针

    • 定义联合类型的指针

      1
      union 联合类型名 *指针变量名;
  • 引用联合类型的成员

    和结构体类型相同,当一个指针指向联合类型的数据时,也可以通过指针引用此联合类型数据的成员。

    1
    2
    指针变量名->联合类型成员名
    (*指针变量名).联合类型变量名

枚举enum

枚举类型的构造

1
enum 枚举类型名{枚举常量1,枚举常量2,...,枚举常量m};

注意:枚举常量的本质是整型常量,进行赋值时不需要加双引号,但外表是标识符,所以遵守标识符的命名规则。

定义枚举类型的变量

1
2
enum 枚举类型名{枚举常量1,枚举常量2,...,枚举常量m}枚举变量名;	//方式1
enum 枚举类型名 枚举变量名; //方式2

枚举变量只能够指代有限个数个数据,而且这些可以被指带的数据在构造枚举类型的时候已经被列举出来了。所以为枚举变量赋值时只能够

1
2
枚举变量 = 枚举常量;
week_day = mon; //上面也提到不需要加双引号的原因了

枚举常量默认值

在C语言中,默认情况下,大括号内枚举常量的值是从0开始的,后面的枚举常量依次增1。当然,也可以认为的修改枚举常量的值

1
enum week{mon,tue = 4,wes,thu,fri = 9,sat,sun};

这样,没被修改的枚举常量的值遵循比前面的枚举常量大1的原则

枚举类型的优点

枚举类型中的枚举常量可以使得枚举常量指代的数据更容易被程序员所理解,写出来的代码也会更加直观。

字符串

字符串,C语言中没有专门的字符串类型,那么也就无法定义字符串变量 。所以,在C语言里,字符串数据是以字符的形式存放在数组当中的。其中,为了确定最后一个字符的位置,C语言设置了字符串结束标志。这个标识是字符\0,它的ASCII码值为0。

初始化方式

1
2
3
4
char arr[6] = {'H','e','l','l','o','\0'};   //方式1
char arr[ ] = {'H','e','l','l','o','\0'}; //方式2
char arr[6] = {"Hello"}; //下面两种方式系统会默认在字符串最后一个字符后面添加结束符"\0"
char arr[6] = "Hello";

注意: 不要越界!不要越界!用于存储字符串数据的字符数组,它的存储空间的个数应该大于字符串中字符的个数加1。越界可能不会报错,但是会修改其他部分数据,严重可能够造成系统混乱。

未完待续。。。

这部分写吐了。。。

文件

未完待续。。。

预编译

预编译指令可以书写在程序中任何一行的开头。它是“指令”,而非语句,所以在指令结尾是不需要加“分号”的,但要以#开头。

1
2
#define			//定义宏
#undef //取消定义宏

它是一种机制,可以让任何数据替换程序代码中的宏名。宏的本质就是一种替换,即在程序被编译之前,代码中的”宏名”发生替换的一种现象。

  • 不带参数的宏

    1
    #define 宏名 表达式 //宏的本质就是一种替换规则,在程序中可以当作“变量名”来使用。
  • 带参数的宏

    1
    #define 标识符(参数1,参数2...) 表达式

    注意:标识符和括号之间一定不要有空格,否则会被系统认为是一个不带参数的宏。使用方法举例如下:

    1
    2
    3
    4
    5
    6
    7
    #include <stdio.h>
    #define ADD(a,b) (a+b)
    int main()
    {
    printf("%d",ADD(45,23)); //可自行试验一下结果
    return 0;
    }
  • 取消宏

    1
    2
    3
    4
    #define PI 3.1415
    ... //该宏作用范围
    #undef PI
    ...
  • 标准宏对象

    标准宏对象,就是C语言自己定义的一些宏。它们的”宏名“是以两个下划线_开始和结束的,中间是大写字母。下面列举几个常用的宏,用的时候当变量或者函数用就可以了。

    1
    2
    3
    4
    _LINE_		//使用此宏的语句是在代码文件中的第几行。值是十进制
    _FILE_ //当前代码文件所在的路径和文件名。值是字符串
    _DATA_ //程序被编译时的日期。值为字符串,格式:mm,dd,yyyy
    _TIME_ //程序被编译时的时间。值时字符串,格式:hh:mm:ss

条件编译

主要指令: #ifdef #if #ifndef。条件编译时为了让程序在各种不同的软硬件环境条件下都能够运行,有效提高程序的可移植性和灵活性。

  • #if 指令 :决定编译的程序段位置

    1
    2
    3
    4
    5
    #if 表达式
    程序段1
    #else
    程序段2
    #endif
  • #ifdef 指令 :如果#ifdef后面的宏被#define定义过,就编译程序段1,否则程序段2

    1
    2
    3
    4
    5
    #ifdef 宏名
    程序段1
    #else
    程序段2
    #endif
  • #ifndef 指令 : 和上面刚好相反

    1
    2
    3
    4
    5
    #ifndef 宏名
    程序段1
    #else
    程序段2
    #endif

文件包含

1
2
#include <xxx.h>
#include "zzz.h"

区分上面可能讲过,找不到就自行百度吧。

您的每一份支持将鼓励我继续创作!
-------------本文结束感谢您的阅读-------------