C语言基础到进阶教程之工作总结

​​​​​​​​​​​​​​

目录

​​​​​​​​​​​​​​

前言

​​​​​​​

1 C语言开发环境搭建与入门

2 数据类型与变量常量

2.1 数据类型

打印基本类型存储空间大小(*)

2.2 变量与常量

2.3 存储类修饰符

强制类型转换(*)

有符号数与无符号数(*)

浮点数的秘密(*)

goto和void分析(*)

const和volatile分析(*)

3 运算符

4 条件语句与循环语句

5 函数与作用域

6 数组、指针与字符串

6.1 数组

6.2 指针

 6.3 字符串

​指针进阶教程(*)

7 结构体、共用体(位域)、枚举

struct和union分析(*)

8 输出输入打印

9 预处理与编译原理

9.1 typedef与宏定义(预处理)

编译和连接过程(*)

条件编译分析(*)

#error 和 #line 使用分析(*)

#pragma 使用分析(*)

# 和 ## 操作符使用分析(*)

  程序执行过程分析(*)

10 高级操作

10.1 文件读写

10.2 内存管理

10.3 可变参数

10.4 命令行操作

10.5 其它进阶教程(*)

11.标准库和常见C案列使用

11.1 标准库

11.2  C案列库

总结



前言

文章主要对一下内容进行基础到进阶介绍和总结:
1.C语言开发环境搭建与入门
2.数据类型与变量常量
3.运算符
4.条件语句与循环语句
5.函数与作用域
6.数组指针与字符串
7.结构体共用体(位域)与枚举
8.输入出与打印
9.预处理与编译原理
10.高级操作
11.标准库和常见C案例库

要想成为C语言的高手,需要熟练掌握C语法中的细节!!!


​​​​​​​

1 C语言开发环境搭建与入门

       C 语言是一种通用的高级语言,最初是由丹尼斯·里奇在贝尔实验室为开发 UNIX 操作系统而设计的。C 语言最开始是于 1972 年在 DEC PDP-11 计算机上被首次实现。 

        C 语言所产生的代码运行速度与汇编语言编写的代码运行速度几乎一样,所以采用 C 语言作为系统开发语言;当今最流行的 Linux 操作系统和 RDBMS(Relational Database Management System:关系数据库管理系统) MySQL 都是使用 C 语言编写的;

编译器安装:

UNIX/Linux 上的安装:请按照 Installing GCC- GNU Project 上的详细说明安装 GCC。

 Windows 上安装 GCC,您需要安装 MinGW。为了安装 MinGW,请访问 MinGW 的主页 mingw-w64.org;完成安装时,您可以从 Windows 命令行上运行 gcc、g++、ar、ranlib、dlltool 和其他一些 GNU 工具

c程序结构:【1】预处理器指令;【2】函数;【3】变量;【4】语句 & 表达式;【5】注释

基本语法:

C 的令牌(Token):C 程序由各种令牌组成,令牌可以是关键字、标识符、常量、字符串值,或者是一个符号;

分号 :在 C 程序中,分号是语句结

标识符:以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)

关键字:下表列出了 C 中的保留字。这些保留字不能作为常量名、变量名或其他标识符名称;

auto,break,case ...


2 数据类型与变量常量

2.1 数据类型

数据类型可以理解为固定内存大小的别名;基本类型:包括整型(int)(二进制0b,十六进制0x)、字符型(char)、浮点型(float)和双精度浮点型(double);void

枚举类型:算术类型,被用来定义在程序中只能赋予其一定的离散整数值的变量

派生类型:包括数组类型、指针类型和结构体类型

类型存储大小值范围
char1 字节-128 到 127 或 0 到 255
unsigned char1 字节0 到 255
signed char1 字节-128 到 127
int2 或 4 字节-32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647
unsigned int2 或 4 字节0 到 65,535 或 0 到 4,294,967,295
short2 字节-32,768 到 32,767
unsigned short2 字节0 到 65,535
long4 字节-2,147,483,648 到 2,147,483,647
unsigned long4 字节0 到 4,294,967,295
类型存储大小值范围精度
float4 字节1.2E-38 到 3.4E+386 位有效位
double8 字节2.3E-308 到 1.7E+30815 位有效位
long double16 字节3.4E-4932 到 1.1E+493219 位有效位

各类型数据可以相互强制转换

打印基本类型存储空间大小(*)

typedef int INT32;
typedef unsigned char BYTE;
INT32 i32;
BYTE b;
printf("%zu, %zu\n", sizeof(INT32), sizeof(i32));     // 4, 4
printf("%zu, %zu\n", sizeof(BYTE), sizeof(b));        // 1, 1

2.2 变量与常量

 变量

       变量是程序可操作的存储区的名称;变量定义就是告诉编译器在何处创建变量的存储,以及如何创建变量的存储;变量声明向编译器保证变量以指定的类型和名称存在,如int a 在声明的时候就已经建立了存储空间;

(1)变量是一段实际连续存储空间的别名

(2)程序中通过变量来申请并命名存储空间

(3)通过变量的名字可以使用存储空间

变量赋值

左值:指向内存位置变量;右值:存储在内存中某些地址的数值或一些立即数;

类型描述
char通常是一个字节(八位), 这是一个整数类型。
int整型,4 个字节,取值范围 -2147483648 到 2147483647。
float

单精度浮点值。单精度是这样的格式,1位符号,8位指数,23位小数。

double

双精度浮点值。双精度是1位符号,11位指数,52位小数。

void表示类型的缺失。

常量

整数常量,带后缀:U 表示无符号整数(unsigned),L 表示长整数(long)

浮点常量:float myFloat = 3.14f;double myDouble = 3.14159;

字符常量:注意转移字符使用;
字符串常量:char myString[] = "Hello, world!"; //系统对字符串常量自动加一个 '\0'

#define和const区别:
  1. 使用 #define 预处理器: #define 可以在程序中定义一个常量,它在编译时会被替换为其对应的值。
  2. 使用 const 关键字:const 关键字用于声明一个只读变量,即该变量的值不能在程序运行时修改。

2.3 存储类修饰符

存储类:auto、register、static、extern

auto 将被修饰的变量存储在栈上;auto属性关键字将被修饰的变量存储在栈上

register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量;变量的访问速度更快;多用于修饰需要频繁使用的局部变量;register不能修饰全局变量;

注意:

(1)不能使用 & 运算符获取register变量的地址,寄存器是没有地址的,只有内存才有地址;否则编译报错,error!

(2)由于register修饰的变量存储在寄存器中,因此该变量必须是CPU寄存器可以接受的值

static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁:

(1)static 关键字指明变量的"静态"属性,局部变量存储在程序静态区(普通的局部变量存储在栈上)

(2)sttaic关键字同时具有"作用域限定符"

  • static修饰的全局变量,作用域是声明该变量的文件中,其它文件不能使用
  • static修饰的函数,作用域是声明该函数的文件中,其它文件不能使用
static int count=10;  //当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内
 
void func(void)
{
/* 'thingy' 是 'func1' 的局部变量 - 只初始化一次
 * 每次调用函数 'func1' 'thingy' 值不会被重置。
 */                
  static int thingy=5;
  thingy++;
  printf(" thingy 为 %d , count 为 %d\n", thingy, count);
}

extern :extern 用于声明"外部"定义的变量和函数

  • extern 变量在其它地方分配空间
  • extern 函数在文其它地方定义

extern用于告诉编译器用C方式编译代码

extern "C"
{
   int fun(int a,int b){
       return a+b;
   }
}


强制类型转换(*)

(1)强制类型转换(变量前需要加类型符号)

void fun_test(void)
{
    short s = 0x1122;

    char c = (short)s;  // 目标类型不能容纳目标值,产生截断,取内存中的最后一个字节,0x22
    int i = (int)s;     // 目标类型能够容纳目标值,结果不变,0x1122
    int j = (int)3.1415;    // 浮点数和整型在内存中的表示方法不同,截断的方法就是舍弃小数部分,取整数部分    // 3                
    unsigned int p = (unsigned int)&ts; // 在32位系统上指针为4字节,是可以的;在64位系统上指针为8字节,会产生截断

    //long l = (long)ts;   // 结构体不能转long,两种类型之间不能进行强制类型转换,编译报错   error: aggregate value used where an integer was expected
    // ts = (struct TS)l;  // 与上面相同,编译报错

    char myChar = 'a';
    int myAsciiValue = (int) myChar; // 将 myChar 转换为 ASCII 值 97
    sum = 17 + myChar;//等于114
}

 (2)隐式类型转换( 编译器主动进行的数据类型转换,不需要加符号说明,程序中出现也很多)

void fun_test(void)
{
    char c = 'a';   // 97
    int i = c;  // 低类型==>高类型,安全 // = 97
    unsigned int j = 0x11223344;
    short s = j; // 高类型==>低类型,不安全导致不正确的结果 // = 0x3344
    printf("sizeof(c+s) = %d\n", sizeof(c+s)); // char和short都会先转化为int,然后进行运算,结果为4
}

有符号数与无符号数(*)

(1)有符号整型有正数和负数,最高位用来标识数据的正负。

  • 最高位为1,表明这个数为负数
  • 最高位为0,表明这个数为正数
void fun(void)
{
    char c = -5;
    short s = 6;
    // 与最高位进行&运算,结果为0表示正数,结果为1表示负数
    printf("%d\n", ((c & 0x80) != 0 ));       // 1 ==> 负数
    printf("%d\n", ((s & 0x8000) != 0));      // 0 ==> 正数
}

 (2)有符号整型的表示法(C语言中整型变量默认为有符号的类型,用unsigned关键字声明整型变量为无符号类型);在计算机内部用补码表示有符号整型

  • 正数的补码为正数本身
  • 负数的补码为负值的绝对值各位取反后加1

    char型整数 5 的补码:0000 0101

    char型整数 -7 的补码:0000 0111 ==> 1111 1000 ==> 1111 1001  ==》0xf9

int i;  // 默认为有符号整数
signed int j;  // 显式声明变量为有符号整数
unsigned int k ;  // 声明为无符号整数

(3) 练习,有符号数比较,和0界点处理要注意以下两点

int main()
{
    unsigned int i = 5;
    int j = -10;
    if ((i + j) > 0){ // 有符号数遇到无符号数,会被看作无符号数
        printf("i+j >= 0\n");  // 程序走进到这个分支
    }
    return 0;
}

int main()
{
    unsigned int i = 0;
    // 当i = 0时,i--变为最大值, MIN_VALUE - 1 ==> MAX_VALUE
    for (i = 9; i >= 0; i--) {// i >= 0一直成立
        printf("i = %u\n", i);//这个一直打印
    }
    return 0;
}

浮点数的秘密(*)

下面以十进制的 8.25 演示一下上述的转换方法。

    ① 将8.25转换为二进制(注意小数的二进制表示方法*)    ==>  1000.01

    ② 用科学计数法表示1000.01(注意这里是二进制,2^3)   ==>  1.00001(2^3)

    ③ 指数3偏移后为:127 + 3 = 130

所以浮点数8.25对应的符号位、指数、尾数分别为:

    符号位:0

    指数:130 ==> 10000010

    小数:00001   // 要将小数转为尾数,需要在后面补0

8.25在内存中的二进制表示为:0 1000 0010 000 01000000 0000 0000 0000 = 0x41040000

goto和void分析(*)

(1)goto已经被打入冷宫

(2)void分析:void 修饰函数返回值和参数是为了表示 "无"

如果函数没有返回值,那么应该将其声明为void;否则编译器默认返回值类型为int;

如果函数没有参数,应该声明其参数为void;否则编译器默认函数可以传递任意多个参数

void指针:【1】void *指针作为左值用于接收任意类型的指针;【2】void *指针作为右值使用时需要进行强制类型转换

const和volatile分析(*)

1. const只读变量

(1)const修饰的变量是只读的,本质上还是变量,并不是真正意义上的常量         ※ const只是告诉编译器该变量不能出现在赋值符号的左边

(2)const修饰的局部变量在栈上分配空间;const修饰的全局变量在全局数据区分配空间

(3)const只在编译期间有用(检查const修饰的变量有没有出现在赋值符号左边,如果有就会编译报错),在运行期间无用

(4)现代C编译器中的const将具有全局生命周期的变量(全局变量 +  static修饰的局部变量)存储于只读存储区

(5)const修饰函数参数和返回值:

【1】const 修饰函数参数表示在函数体内不希望改变参数的值;

【2】const 修饰函数返回值表示返回值不可改变,多用于返回指针的情形;

【3】C 语言的字符串字变量存储于只读存储区中,在程序中需要使用 const char* 指针

2.volatile 分析

(1)volatile 可理解为 "编译器警告指示字"

(2)volatile 告诉编译器必须每次去内存中取变量值

(3)volatile 主要修饰可能被多个线程访问的变量

(4)volatile 也可以修饰可能被未知因数更改的变量


3 运算符

  • 算术运算符:+、-、*、/、%、++、--
  • 关系运算符:A=10;B=30;
    (A == B) 为假
    (A != B) 为真
    (A > B) 为假
    (A < B) 为真
    (A >= B) 为假
    (A <= B) 为真
  • 逻辑运算符A=1;B=0;
    (A && B) 为假
    (A || B) 为真
    !(A && B) 为真
  • 位运算符:位运算符作用于位,并逐位执行操作。
    &、 | 和 ^ 、~、<<、>>
  • 赋值运算符:=、+=、-=、*=、/=、%=、<<=、>>=、&=、^=、|=
  • 杂项运算符
    运算符描述实例
    sizeof()返回变量的大小。sizeof(a) 将返回 4,其中 a 是整数。
    &返回变量的地址。&a; 将给出变量的实际地址。
    *指向一个变量。*a; 将指向一个变量。
    ? :条件表达式如果条件为真 ? 则值为 X : 否则值为 Y

C 中的运算符优先级:乘除运算符具有比加减运算符更高的优先级;其他加括号;

4 条件语句与循环语句

4.1 条件语句

if(boolean_expression 1){
   /* 当布尔表达式 1 为真时执行 */
}else if( boolean_expression 2){
   /* 当布尔表达式 2 为真时执行 */
}else {
   /* 当上面条件都不为真时执行 */
}
switch(表达式)
{
    case 常量表达式1:语句1;
    case 常量表达式2:语句2;
    ...
    default:语句n+1;
}

4.2 循环语句

 循环类型

int fun (void)
{
   /* 局部变量定义 */
   int a = 10;

   /* while 循环执行 */
   while( a < 20 )
   {
      printf("a 的值: %d\n", a);
      a++;
   }
 
   return 0;
}

while 循环适用于在循环之前检查条件,而 do while 循环适用于至少执行一次循环体,不管条件是否成立 

int fun (void)
{
   /* for 循环执行 */
   for( int a = 10; a < 20; a = a + 1 )
   {
      printf("a 的值: %d\n", a);
   }
 
   return 0;
}
int fun (void)
{
   /* 局部变量定义 */
   int a = 10;

   /* do 循环执行,在条件被测试之前至少执行一次 */
   do
   {
       printf("a 的值: %d\n", a);
       a = a + 1;
   }while( a < 20 );
 
   return 0;
}

循环控制语句,有break、continue、goto:

C 语言中 break 语句有以下两种用法:

  1. 当 break 语句出现在一个循环内时,循环会立即终止,且程序流将继续执行紧接着循环的下一条语句。
  2. 它可用于终止 switch 语句中的一个 case;

continue 会跳过当前循环中的代码,强迫开始下一次循环;

goto 语句允许把控制无条件转移到同一函数内的被标记的语句

goto label;
... 
label: 
statement;

5 函数与作用域

//2. 函数声明 ,函数声明会告诉编译器函数名称及如何调用函数
int max(int num1, int num2);
 
int main ()
{
   /* 局部变量定义 */
   int a = 100;
   int b = 200;
   int ret = max(a, b); //3.调用函数,包括传参和返回值返回
 
   printf( "Max value is : %d\n", ret );
 
   return 0;
}

int max(int num1, int num2) //1.函数定义,形式参数被当作该函数内的局部变量
{
   int result;   /* 局部变量声明 */
 
   if (num1 > num2)
      result = num1;
   else
      result = num2;
 
   return result; //函数返回值
}

作用域:局部变量和全局变量的名称可以相同,但是在函数内,如果两个名字相同,会使用局部变量值,全局变量不会被使用;

函数的意义

(1)C语言中就是使用函数实现算法:程序=数据+算法;C程序=数据+函数;

(2) 函数的意义,C语言中的模块化   ==>  使用函数完成模块化编程

(3)面向过程的程序设计

【1】面向过程是一种以过程(过程即解决问题的过程)为中心的编程思想。首先将复杂的问题分解为一个个容易解决的问题,分解过后的问题可以按照步骤一步步完成。

【2】函数是面向过程在C语言中的体现,解决问题的每个步骤可以用函数来实现

【3】声明的意义在于告诉编译器程序单元的存在,定义则明确指示程序单元的意义

8.函数的参数:

(1)函数参数在本质上与局部变量相同,都在栈上分配空间

(2)函数参数的初始值是函数调用时的实参值

9.函数与宏

(1)宏是由预处理器直接替换展开的,编译器不知道宏的存在,因此参数无法进行类型检查

        函数是由编译器直接编译的实体,调用行为由编译器决定

(2)多次使用宏会增大代码量,最终导致可执行程序的体积增大,对于嵌入式设备而言,设备资源有限,这个还是比较重要的

        函数是跳转执行的,内存中只有一份函数体存在,不存在宏的问题

(3)宏的效率比函数高,因为宏是文本替换,没有调用开销       //  虽然宏的效率比函数稍高但副作用很大,因此,可以用函数完成的功能绝对不用宏;

(4)假如使用 RESET(10, len),这个在编译期间是不错报错的,宏不会检查参数的类型

10.函数的设计原则

1. 函数从意义上应该是一个独立的功能模块

2. 函数名要在一定程度上反映函数的功能

3. 函数参数要能够体现参数的意义

4. 尽量避免在函数中使用全局变量

5. 当函数参数不应该在函数体内部修改时,应加上const声明

6. 如果参数是指针,且仅作输入参数,则应加上const声明

7. 不能省略返回值的类型

如果函数没有返回值,那么应声明为void类型

8. 对参数进行有效性检查

9. 不要返回指向“栈内存”的指针

10. 函数体的规模要小,尽量控制在80行代码之内

11. 相同的输入对应相同的输出,避免函数带有“记忆”功能

12. 避免函数有过多的参数,参数个数尽量控制在4个以内

13. 有时候函数不需要返回值,但为了增加灵活性,如支持链式表达,可以附加返回值

14. 函数名与返回值类型在语义上不可冲突


6 数组、指针与字符串

6.1 数组

声明数组:double balance[10];

初始化数组:double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};

访问数组:double salary = balance[9];

获取数组长度(个数):

int numbers[] = {1, 2, 3, 4, 5};
int length = sizeof(numbers) / sizeof(numbers[0]);
//或者:
#define LENGTH(array) (sizeof(array) / sizeof(array[0]))
int length = LENGTH(numbers);

二维数组:

int fun(void)
{
   int a[5][2] = { {0,0}, {1,2}, {2,4}, {3,6},{4,8}};//初始化二维数组,5 行 2 列
   int i, j;
   for ( i = 0; i < 5; i++ ){
      for ( j = 0; j < 2; j++ ){
         int val = a[i][j];//访问二维数组元素
         printf("a[%d][%d] = %d\n", i,j, val);
      }
   }
   return 0;
}

数组作为参数:

double getAverage(int arr[], int size)
{
  int    i;
  double avg;
  double sum;
 
  for (i = 0; i < size; ++i){
    sum += arr[i];
  }
  avg = sum / size;

  return avg;
}

void test(void)//调用:
{
   int balance[5] = {1000, 2, 3, 17, 50};
   double avg = getAverage( balance, 5 ) ;   /* 传递一个指向数组的指针作为参数 */
}

数组作为返回值、指向数组的指针:

int * getRandom( )
{
  static int  r[10];
  int i;
 
  for ( i = 0; i < 10; ++i){
     r[i] = rand();
     printf( "r[%d] = %d\n", i, r[i]);
  }
 
  return r;
}

int fun_test()//调用
{
   int i;
   int *p = getRandom();   /* 一个指向整数的指针,指向数组的指针
   for ( i = 0; i < 10; i++ ){
       printf( "*(p + %d) : %d\n", i, *(p + i));//使用p作为地址的数组值
   }
   return 0;
}

静态数组与动态数组:

//静态数组:
int array[] = {1, 2, 3, 4, 5};
int length = sizeof(array) / sizeof(array[0]);

//动态数组:
int size = 5;
int *dynamicArray = (int *)malloc(size * sizeof(int)); // 动态数组内存分配
// 使用动态数组
free(dynamicArray); // 动态数组内存释放

6.2 指针

 (1)指针基本使用

指针表示了在内存中的一个地址,即变量的地址;指针变量是用来存放内存地址的变量;

int    *ip;    /* 一个整型的指针 */
double *dp;    /* 一个 double 型的指针 */
float  *fp;    /* 一个浮点型的指针 */
char   *ch;    /* 一个字符型的指针 */
int  *ptr = NULL;//空指针=0;操作系统上,程序不允许访问地址为 0 的内存

int fun()
{
   int  var = 20;   /* 实际变量的声明 */
   int  *ip;        /* 指针变量的声明 */
 
   ip = &var;  /* 在指针变量中存储 var 的地址 */
   printf("var 变量的地址: %p\n", &var  );
   printf("ip 变量存储的地址: %p\n", ip );/* 在指针变量中存储的地址 */
   printf("*ip 变量的值: %d\n", *ip );   /* 使用指针访问值 */
 
   return 0;
}

(2)指针运算p++ ;p--;指针比较

const int MAX = 3;
int fun()
{
   int  var[] = {10, 100, 200};
   int  i, *ptr;

   ptr = var;   /* 指针中的数组地址 */
   for ( i = 0; i < MAX; i++){
      ptr++;
   }
   ptr = &var[MAX-1];
   for ( i = MAX; i > 0; i--){
      ptr--;
   }

   ptr = var;
   i = 0;
   while ( ptr <= &var[MAX - 1] ){ //比较
      printf("存储值:var[%d] = %d\n", i, *ptr );
      ptr++;
      i++;
   }
}

 (3)指针数组

定义:int *ptr[3];//其实就是定义3个int型的指针,ptr[0]、ptr[1]、ptr[2];
一个指向字符的指针数组来存储一个字符串列表

const int MAX = 4;
 int fun()
{
   const char *names[] = {
                   "Zara Ali",
                   "Hina Ali",
                   "Nuha Ali",
                   "Sara Ali",
   };
   int i = 0;
   for ( i = 0; i < MAX; i++){
      printf("Value of names[%d] = %s\n", i, names[i] );
   }
   return 0;
}

 (4)指向指针的指针:一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置

int fun()
{
   int  V;
   int  *Pt1;
   int  **Pt2;
   V = 100;
   Pt1 = &V;  /* 获取 V 的地址 */
   Pt2 = &Pt1;  /* 使用运算符 & 获取 Pt1 的地址 */
 
   /* 使用 pptr 获取值 */
   printf("var = %d\n", V );     //var = 100
   printf("Pt1 = %p\n", Pt1 );   //Pt1 = 0x7ffee2d5e8d8
   printf("*Pt1 = %d\n", *Pt1 ); //*Pt1 = 100
   printf("Pt2 = %p\n", Pt2 );   //Pt2 = 0x7ffee2d5e8d0
   printf("**Pt2 = %d\n", **Pt2);//**Pt2 = 100
   return 0;
}

(5) 传递指针给函数:double getAverage(int *arr, int size);类似数组传参

(6)从函数返回指针:int * myFunction();类似从函数返回数组;

(7) 函数指针和回调函数:

typedef int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型

fun_ptr p = &max; // &可以省略

//或者:
int (* p)(int, int) = & max; // &可以省略

void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
    for (size_t i=0; i<arraySize; i++)
        array[i] = getNextValue();
}
int getNextRandomValue(void)// 获取随机值
{
    return rand();
}
int fun(void)
{
    int myarray[10];
    populate_array(myarray, 10, getNextRandomValue);//printf("%d ", myarray[i]);
    return 0;
}

 6.3 字符串

在C语言中,双引号引用的单个或多个字符是一种特殊的字面量,C语言中通过特殊的字符数组模拟字符串:

  • 存储于程序的全局只读存储区
  • 本质为字符数组,编译器自动在结尾加上 '\0' 字符;  // 回忆前面学过的转义符 \ ,'\0' 即八进制的0表示的字符,八进制的0在内存中就是0;
  • char s[] = "Hello\0World";  // 以第一个出现的'\0'作为字符串的结束符,strlen(s)为 5
#include <stdio.h>
#include <string.h>

char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};//字符串定义
char site[] = "RUNOOB";//自动存储了空字符 \0

//字符串常用标准库函数
int fun()
{
   char str1[14] = "runoob";
   char str2[14] = "google";
   char str3[14];

   if(strcmp(str1,str2) != 0){
        printf("not the same\n");
   }
   strcpy(str3, str1);   /* 复制 str1 到 str3 */
   printf("strcpy( str3, str1) :  %s\n", str3 );
 
   strcat( str1, str2);   /* 连接 str1 和 str2 */
   printf("strcat( str1, str2):   %s\n", str1 );
 
   int len = strlen(str1);   /* 连接后,str1 的总长度 */
   printf("strlen(str1) :  %d\n", len );

  if(strncmp(str1, str2, 4) == 0)//比较前4个字符

  strncat(str1, str2, 4);//连接 str1 和 str2,连接4个字符
  memset(str,0,7);//清0操作
   return 0;
}

//memmove() 能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,复制后源区域的内容会被更改。如果目标区域与源区域没有重叠,则和 memcpy() 函数功能相同。
void *memcpy(void *str1, const void *str2, size_t n) 从存储区 str2 复制 n 个字节到存储区 str1 

int memcmp(const void *str1, const void *str2, size_t n)) 把存储区 str1 和存储区 str2 的前 n 个字节进行比较 


指针进阶教程(*)


1.指针的本质分析:程序中的变量只是一段存储空间的别名,那么是不是必须通过这个别名才能使用这段内存空间?

* 号的意义
(1)在指针声明时,*号表示所声明的变量为指针

(2)在指针使用时,*号表示取指针所指向的内存空间中的值

2.数组的本质分析:数组是相同类型的变量的有序集合数组名作为作为 sizeof 操作符的参数,表示整个数组的大小
数组名作为 & 运算符的参数,表示整个数组的地址

int main()
{
    int a[5] = {0};
    printf("a = %p\n", a);          // 0x7ffc7425dbb0
    printf("&a = %p\n", &a);        // 0x7ffc7425dbb0
    printf("&a[0] = %p\n", &a[0]);  // 0x7ffc7425dbb0
    p = a;
    printf("&p = %p\n", &p);  // 指针p的地址, 0x7ffe40054a68

    printf("sizeof(a) = %zu\n", sizeof(a));  //数组的大小, 8
    printf("sizeof(p) = %zu\n", sizeof(p));  //指针的大小为8(64位系统)
    return 0;
}

 2.指针运算

(1)指针是一种特殊的变量,当指针p指向一个同类型的数组元素时,p + 1将指向当前元素的下一个元素,p -1指向当前元素的上一个元素。与整数的运算规则为:

 p+n; <==>(unsigned long)p + n*sizeof(*p)

(2)指针之间只支持减法运算,参与减法运算的指针类型必须相同。指针也可以进行关系运算(<   <=   >   >=   ==   !=);

#define DIM(a)  (sizeof(a) / sizeof(*a))// 数组元素的个数
int main()
{
    char s[]={'H','e','l','l','o'};
    char* pBegin = s;
    char* pEnd = s + DIM(s);   // 数组末尾的后一个地址
    char* p = NULL;

    printf("pBegin  = %p\n", pBegin);
    printf("pEnd  = %p\n", pEnd);
    printf("Size: %ld\n", pEnd - pBegin); // 5
    for (p = pBegin; p < pEnd; p++){  // Hello
         printf("%c",*p);
    }
    return 0;
}

(3)数组名:a 和 &a 的区别

【1】a 为数组首元素的地址

【2】&a 为整个数组的地址

【3】a 和 &a 的区别在于指针运算,前者针对的是数组的元素,后者针对的是整个数组

指针运行经典问题:

void fun()
{
    int a[5] = {1, 2, 3, 4, 5};
    int* p1 = (int*)(&a + 1);       // 指向数组元素5的后一个位置
    int* p2 = (int*)((int)a + 1);   // 指向数组 (起始地址 + 1字节) 处,这里是整数运算,不是指针运算
    int* p3 = (int*)(a + 1);        // 指向第2个元素
    printf("%d\n", p1[-1]);   // 5
    printf("%d\n", p2[0]);    // 33554432
    printf("%d\n", p3[1]);    // 2
}

【4】数组作为函数参数时,编译器将其编译成对应的指针    // 数组长度信息没有意义,都是退化为指针 ;如:void func1(char a[5]) // char a[5] ==> char *a  // printf("In func1:sizeof(a) = %d\n", sizeof(a)); // 8 ==> a退化为指针

3.数组指针和指针数组分析

(1)数组指针,顾名思义就是一个指针,只不过该指针用于指向一个数组

typedef float (AFLOAT10)[10];
AFLOAT10* pf = &fArray;
for(i=0; i<10; i++){
    (*pf)[i] = i;  // fArray[i] = i;
}

(2)指针数组,顾名思义就是一个数组,该数组的元素类型为指针类型;指针数组的定义:

type *PArray[n];

const char* keyword[] = {   // 这里就是一个字符指针数组
   "do",
   "for",
   "while",
};

 4.函数与指针分析

(1)函数指针:

【1】函数的类型由返回值参数类型参数个数共同决定

【2】函数指针用于指向一个函数,函数名是执行函数体的入口地址

typedef int (FUNC)(int);    // 使用typedef重命名函数类型
int test(int i)
{
    return i * i;
}
FUNC* pt = test;    // 函数名就是函数体的入口地址

(2)回调函数:回调函数是利用函数指针实现的一种调用机制

typedef int (*Weapon)(int);
void fight(Weapon wp,int arg)// 使用回调函数动态切换装备
{ 
    int  result = wp(arg);
    printf("Boss loss:%d\n",result);
}
int knife(int n) // 使用刀作为武器
{
     return n+1;   
}
int sword(int n)// 使用剑作为武器
{
    return n+2;   
}
int gun(int n)// 使用枪作为武器
{
    return n+3;   
}
 
int main()
{    
    fight(knife, 3);    // 用刀砍3次
    fight(sword, 4);    // 用剑刺4次
    fight(gun, 5);      // 开枪5次
    return 0;
}
 

 5.动态内存分配,意义:

(1)C语言中的一切操作都是基于内存的

(2)变量和数组都是内存的别名内存分配由编译器在编译期间决定定似数组的时候必须指定数组长度数组长度是在编译器就必须确定的;

(3)malloc/calloc/realloc操作

6.内存操作经典问题分析

(1)野指针指的是 指针变量的值是非法的内存地址,操作野指针会导致程序运行出;野指针不是NULL指针而是指向了非法地址;NULL指针并没有危害而且很好判断和调试。异常情况有:【1】局部指针变量没有被初始化;【2】指针所指向的变量在指针使用之前已经被销;【3】 进行了错误的指针运算,比如越界访问内存;【4】进行了错误的强制类型转换;结构体成员指针未初始化或没有分配足够内存空间;


7 结构体、共用体(位域)、枚举

(1)枚举:

enum DAY  //枚举定义
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};
//使用,可以用于变量初始化
void fun(enum DAY day)
{
    switch( day )
    {
       case TUE:
            break;
       case WED:
            break;
        case FRI:
           break;
    }
}
//调用:ret = fun(TUE);
//遍历
void test(void)
{
    enum DAY day
    for (day = MON; day <= SUN; day++) {  // 遍历枚举元素
        printf("枚举元素:%d \n", day);
    }
}


//枚举可以不连续,但是不能遍历
enum
{
    ENUM_0,
    ENUM_10 = 10,
    ENUM_11
};
//枚举强转
    int a = 1;
    enum day weekend;
    weekend = ( enum day ) a;  //类型转换


 (2)结构体:定义、初始化,访问;结构体指针

typedef struct _norflash_ctrl{
    u8 data_width;
    u8 clk;
}norflash_ctrl_t;

typedef struct{
    u16 u16crc;
    u16 work_mode;
    union {
        u32 test_config;
        struct {
            u32 trim_check: 1;
            u32 is_ufw: 1;
            u32 io_test: 1;
            u32 dc_test: 1;
        };
    };
    norflash_ctrl_t norflash_ctrl;
} __attribute__((aligned(4))) testmode_params_t ;//定义

testmode_params_t g_testmode_params;
int fun()
{
    g_testmode_params.norflash_ctrl.data_width= 3;//初始化及访问
    testmode_params_t *testmode_params = &g_testmode_params;
    testmode_params -> work_mode = 2;//结构体指针初始化及访问
    testmode_params -> trim_check= 1;//共用体位域访问
}
 
//结构体做形参:int fun(testmode_params_t *testmode_params){}

 (3)共用体:union只分配最大成员的空间,所有成员共享这个空间

union Data
{
   int i;
   float f;
   char  str[20];
};
int fun( )
{
   union Data data;//定义    
 
   data.i = 10;   //访问
   data.f = 220.0;//因为共用,复制后,data.i就等于220
   strcpy( data.str, "C Programming");
   return 0;
}

(4)位域:特殊的结构体成员,允许按位对成员进行定义,指定其占用的位数;访问和指针访问跟结构体一致

struct和union分析(*)

(1)对于空结构体:gcc编译器     ==>   编译通过,且空结构体的大小为0。 

(2)柔性数组

1 struct SoftArray {
2     int len;
3     int array[];    // 结构体最后一个元素为柔性数组
4 };
sizeof(struct SoftArray)// 等于4;array 是不占用内存空间的,通过打印 len 和 array 的地址可以看出来,array 是一个标识符,可以当做指针首地址使用

(3)union:参考结构体部分


8 输出输入打印

都需要包含头文件:#include <stdio.h>

int getchar(void) 函数从屏幕读取下一个可用的字符,并把它返回为一个整数;

int putchar(int c) 函数把字符输出到屏幕上,并返回相同的字符

2.

char *gets(char *s) 函数从 stdin 读取一行到 s 所指向的缓冲区,直到一个终止符或 EOF

int puts(const char *s) 函数把字符串 s 和一个尾随的换行符写入到 stdout

3.

int scanf(const char *format, ...) 函数从标准输入流 stdin 读取输入,并根据提供的 format 来浏览输入

int printf(const char *format, ...) 函数把输出写入到标准输出流 stdout ,并根据提供的格式产生输出。

format 可以是一个简单的常量字符串,但是您可以分别指定 %s、%d、%c、%f 等来输出或读取字符串、整数、字符或浮点数。

9 预处理与编译原理

9.1 typedef与宏定义(预处理)

1.typedef :typedef 用于给一个已经存在的数据类型重命名,typedef 不能产生新的类型

typedef unsigned char BYTE;使用BYTE代替unsigned char ,拿BYTE定义变量
typedef int (*fun_ptr)(int,int);//fun_ptr代替函数指针,拿fun_ptr定义函数指针变量
typedef struct Books
{
   char  title[50];
   char  author[50];
} Book;//Book代替整个结构体;可以拿Book整个去定义结构体变量

typedef 和 #define 区别:

  • typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
  • typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。

2.预处理

#define; #include; #undef; #ifdef; #ifndef; #if; #else; #elif; #endif; 等等

#error:当遇到标准错误时,输出错误消息

#pragma:使用标准化方法,向编译器发布特殊的命令到编译器中

#ifdef BURN_CONFIG_ENABLE_BR27

#elif defined PUSH_TESTMODE_INTERFACE2_ENABLE

#else

#endif

//
#if !defined (MESSAGE)
   #define MESSAGE "You wish!"
#endif

3.预定义宏使用

#include <stdio.h>
void fun()
{
   printf("File :%s\n", __FILE__ );//打印前文件名,一个字符串常量
   printf("Date :%s\n", __DATE__ );//打印当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量
   printf("Time :%s\n", __TIME__ );//打印当前时间,一个以 "HH:MM:SS" 格式表示的字符常量
   printf("Line :%d\n", __LINE__ );//打印当前行号,一个十进制常量
   printf("ANSI :%d\n", __STDC__ );//当编译器以 ANSI 标准编译时,则定义为 1
   printf("ANSI :%d\n", __FUNCTION__ );//打印当前所在的函数名
}

4.预处理器运算符

宏延续运算符(\);字符串常量化运算符(#);标记粘贴运算符(##)

#define  message_for(a, b)  \
    printf(#a " and " #b ": We love you!\n")
 message_for(Carole, Debra);//输出:Carole and Debra: We love you!

#define tokenpaster(n) printf ("token" #n " = %d", token##n)
tokenpaster(34);//等价 printf ("token34 = %d", token34);

5.参数化的宏 :

#define MAX(x,y) ((x) > (y) ? (x) : (y))

#define square(x) ((x) * (x))

编译和连接过程(*)

(1)编译器包含了:预处理器、编译器、汇编器、链接器

编译过程:【file.c、file.h】-->预处理器-->【file.i】-->编译器-->【file.s】-->汇编器-->【file.o】

(2)具体实现过程

【1】预处理:预处理由预处理器完成,预处理指令示例:gcc -E file.c -o file.i

(1)处理所有的注释,以空格代替

(2)将所有的 #define 删除,并且展开所有的宏定义

(3)处理条件编译指令 #if,#ifdef,#elif,#else,#endif

(4)处理 #include,展看被包含的文件

(5)保留编译器需要使用的 #pragma 指令

【2】编译:编译由编译器(狭义)完成,编译指令示例:gcc -S file.i -o file.s

(1)对预处理文件进行词法分析语法分析语义分析

  • 词法分析:分析关键字、标识符、立即数等是否合法
  • 语法分析:分析表达式是否遵循词法规则
  • 语义分析:在语法分析的基础上进一步分析表达式是否合法

(2)分析结束后进行代码优化生成相应的汇编代码文件

【3】 汇编:汇编由汇编器完成,汇编指令示例:gcc -c file.s -o file.o    file.o是二进制文件

(1)汇编器将汇编代码转变为机器的可执行指令

(2)每条汇编语句几乎都对应一条机器指令

【4】链接:通过连接器生成最终的可执行文件

(1)  链接器的主要作用就是处理各个模块(目标文件和库文件)之间的相互引用,使得各个模块之间能够正确的衔接。

(2)  静态链接:由链接器在链接时将库的内容直接加入到可执行程序中(库中,只有被使用的函数才会被链接进去,未使用的不会被链接到可执行程序中!)。

(3)Linux下静态库的创建和使用:

  • 编译静态库源码: gcc  -c  lib.c  -o  lib.o
  • 生成静态库文件: ar  -q  lib.a  lib.o
  • 使用静态库编译: gcc  main.c  lib.a  -o  main.out

(4)动态链接: 可执行程序在运行时才动态加载库进行链接,库内容不会进入可执行程序当中:

(5)Linux下动态库的创建和使用:

  • 编译动态库源码: gcc -shared  -fPIC  dlib.c -o dlib.so
  • 使用动态库编译: gcc main.c  -ldl  -o  main.out       // 使用dlopen、dlsym、dlclose需要用到dl这个动态链接库,-ldl;(dlib.so和main.out需要放置同级目录)
  • 动态库相关的系统调用

   — dlopen:打开动态库文件

   — dlsym:  查找动态库中的函数并返回调用地址

   — dlclose:关闭动态库文件

(6)为什么需要动态链接库呢?

方便程序的更新,当程序有bug或者程序功能需要更新时,不用更新应用程序,只需要更新动态库文件即可,非常方便;如果是静态链接的话无法更新部分应用程序,需要全部重新编译!

条件编译分析(*)

(1)基本概念:条件编译是在预处理阶段由预处理器完成的,预处理器根据条件编译指令选择使用哪些代码

(2)#include的本质

(1)#include 的本质是将已经存在的头文件的内容嵌入到当前文件中

(2)如果#include包含的头文件中又包含了其它头文件,那么该文件的内容也会被嵌入到当前文件中

(3)条件编译的意义

(1)条件编译使得我们可以按照不同的条件编译不同的代码段,因而可以产生不同的目标代码

(2)#if...#else...#endif 被预处理器处理;而 if...else... 语句被编译器处理,必然会被编译到目标代码中

(3)实际工程中条件编译主要用于以下两种情况:

   ① 不同的产品线共用一份代码

   ② 区分编译 产品的调试版和发布版

#define DEBUG 1    // 区分版本时调试版还是发布版
#define HIGH  1    // 区分版本是高配版还是低配版

#if defined (MAX) && defined (MIN) && defined (AVE)

printf("三个宏已全部定义\n");

#elif MAX==10

printf("三个宏未全部定义\n");

#endif

if defined(VAX) && defined(UNIX) && !defined(DEBUG) 这个例子就是当VAX和UNIX都定义了,并且DEBUG没有被定义的情况下,则条件成立。

#if !defined

#if !defined与#ifndef类似,都是用来判断宏没有被定义。
区别在于#if !defined可以判断多个(类似前面的#if defined

 

#define FUN_A 0
#define FUN_B 1
#define FUN_C 2

#define FUNCTION FUN_A

#if (FUNCTION == FUN_A)
//代码1
#elif (FUNCTION == FUN_B)
//代码2
#elif (FUNCTION == FUN_C)
//代码3
#endif
 /
#define FUN_A 1
#define FUN_B 1

#if (FUN_A && FUN_B)
//代码1
#endif

#if (FUNCTION < 5)
//代码1
#endif

#ifdef ABC
// ... codes while definded ABC
#elif (CODE_VERSION > 2)
// ... codes while CODE_VERSION > 2
#else
// ... remained cases
#endif // #ifdef ABC

 

#error 和 #line 使用分析(*)

(1)#error 的用法:#error 是一个预处理器指示字,用于生成一个编译错误消息,这个消息最终会传递到编译器(gcc);类似的,#warning 用于生成编译警告错误

#ifndef __cplusplus//如果没有定义这个,编译时候会输出如下信息
   #error This file should be processed with C++ compiler
#endif

void f()//如果人为没有定义某种情况,编译打印具体报错信息
{
#if (PRODUCT == 1)
    printf("This is a low level product!\n");
#elif (PROUDCT == 2)
    printf("This is a middle level product!\n");
#elif (PRODUCT == 3)
    printf("This is a high level product!\n");
#else
    // 如果PRODUCT未定义或定义了但!=1 != 2 != 3
    #error The PRODUCT macro is NOT defined!
#endif
}

(2)#line 编译指示字的本质是重定义 __LINE__ __FILE__;用于定位哪行发生错误,这样就定位了是哪个人写的;

#pragma 使用分析(*)

(1)#pragma message指令:与 #error 和 #warning不同,#pragma message仅仅代表一条编译消息,不代表程序错误

(2)#pragma once指令:#pragma once用于保证头文件只被编译一次;

(3)#pragma pack指令:  #pragma pack( ) 可以改变编译器的默认对齐方式(编译器默认为4字节对齐)。对齐参数:每个结构体成员按照 其类型大小pack参数 中较小的一个进行对齐(如果该成员也为结构体,那就取其内部长度最大的数据成员作为其大小)

#pragma pack(2) // 以2字节对齐
struct Test1
{               // 对齐参数    偏移地址    大小
    char  c1;   // 1          0         1
    short s;    // 2          2         2
    char  c2;   // 1          4         1
    int   i;    // 2          6         4
};              // 在2字节对齐下,该结构体大小为10字节
#pragma pack()

#pragma pack(4) // 以4字节对齐
struct Test2
{               // 对齐参数    偏移地址    大小
    char  c1;   // 1          0          1
    char  c2;   // 1          1          1
    short s;    // 2          2          2
    int   i;    // 4          4          4
};              // 在4字节对齐下,该结构体大小为8字节
#pragma pack()

# 和 ## 操作符使用分析(*)

(1)# 运算符:# 运算符用于在预处理期将宏参数转换为字符串,即加上双引号   

#define STRING(x)  #x    // 将宏参数x转换为字符串
int main()
{
    printf("%s\n", STRING(Hello World!));   // "Hello World!"
    printf("%s\n", STRING(100));            // "100"
}

(2)## 运算符:## 运算符用于在预处理期粘连两个标识符        (## 运算符的操作对象是标识符)

#define NAME(n) name##n
void fun()
{
    int NAME(1); // name1;
    int NAME(2); // name2;
    NAME(1) = 1;
    NAME(2) = 2;
    printf("%d\n", NAME(1));  // 1
    printf("%d\n", NAME(2));  // 2
}

  程序执行过程分析(*)


10 高级操作


10.1 文件读写

1.打开文件:(mode参数:"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b")加b代表二进制;

FILE *fopen( const char *filename, const char *mode );

2.关闭文件

 int fclose( FILE *fp );

3.写入文件

int fputc( int c, FILE *fp );

4.读取文件

int fgetc( FILE * fp );

5.其它:二进制 I/O 函数读写;嵌入式软件读写依据对应的文件系统来操作


10.2 内存管理

char *description = (char *)malloc( 30 * sizeof(char) );/* 动态分配内存 */

description = (char *) realloc( description, 100 * sizeof(char) );  
/* 想要存储更大的描述信息 */

free(description);   /* 使用 free() 函数释放内存 */

calloc(200, sizeof(char));//在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0


10.3 可变参数

double average(int num,...)
{

}


10.4 命令行操作

int main( int argc, char *argv[] )  
{
   printf("Program name %s\n", argv[0]);
   printf("The argument supplied is %s\n", argv[1]);
}
//执行
$./a.out "testing1 testing2"

Progranm name ./a.out
The argument supplied is testing1 testing2

10.5 其它进阶教程(*)

1.头文件

include <> 与include "" 的区别:

#include < > 引用的是编译器的类库路径里面的头文件。

#include " " 引用的是你程序目录的相对路径中的头文件,如果在程序目录没有找到引用的头文件则到编译器的类库路径的目录下找该头文件。

2.错误处理

在进行除法运算时,如果不检查除数是否为零,则会导致一个运行时错误

3.sizeof关键字的用法

(1)sizeof是C语言的一个内置关键字而不是函数,初学者往往因为sizeof后面的一对括号将其误认为是函数(2)sizeof 用于计算 类型 或 变量 所占的内存大小;(3)用法:sizeof(type)

4.接续符(\)和转移字符:【1】使用接续符,宏代码块更美观,可读性更强;【2】当反斜杠作为转义字符使用时必须出现在单引号或者双引号之间;如:char* p = "\141\t\x62";

5. C语言中的位运算符直接对 bit 位进行操作,其效率最高

6.++和--操作符

(1)前置++/--对应的两条汇编指令:变量自增(减)1;然后取变量值

(2)后置++/--对应的两条汇编指令:先取变量值;然后变量自增(减)1

(3)不同编译器对于 ++ 和 -- 操作符的混合运算的处理并不相同,如r = (i++) + (i++) + (i++);在实际工程开发中不要将 ++ 和 -- 参与到混合运算中!!!

7.三目运算符和逗号表达式:

【1】三目运算符(a ? b : c)可以作为逻辑运算的载体

【2】三目运算符的本质就是 if...else... 语句,只不过三目运算符格式更加方便优雅

【3】逗号表达式:二维数组初始化中用

8.\0的十六进制是多少


11.标准库和常见C案列使用

11.1 标准库

1.#include <assert.h>

可以将断言看作是异常处理的一种高级形式;原型定义:
void assert( int expression );
如果expression的值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用abort 来终止程序运行。

    int a = 10;
    int b = 100;
    assert( a>b );//编译时候会报错

2.#include <ctype.h>

int tolower(int c)//该函数把大写字母转换为小写字母。
int toupper(int c)//该函数把小写字母转换为大写字母。

 3.#include <limits.h>

printf("The minimum value of SIGNED CHAR = %d\n", SCHAR_MIN);
printf("The maximum value of SIGNED CHAR = %d\n", SCHAR_MAX);
The number of bits in CHAR_BIT= 8
The minimum value of SCHAR_MIN = -128
The maximum value of SCHAR_MAX = 127
The maximum value of UCHAR_MAX = 255
The minimum value of SHRT_MIN = -32768
The maximum value of SHRT_MAX = 32767
The minimum value of INT_MIN = -2147483648
The maximum value of INT_MAX = 2147483647
The minimum value of CHAR_MIN = -128
The maximum value of CHAR_MAX = 127
The minimum value of LONG_MIN = -9223372036854775808
The maximum value of LONG_MAX = 9223372036854775807

4.#include <string.h>

参考字符串章节

5.#include <stdio.h>

sprintf(str, "Pi 的值 = %f", M_PI);//Pi 的值 = 3.141593
int printf(const char *format, ...)
int scanf(const char *format, ...)
int putchar(int char)//把参数 char 指定的字符(一个无符号字符)写入到标准输出 stdout 中。

6.#include <stdlib.h>

int atoi(const char *str)//把参数 str 所指向的字符串转换为一个整数(类型为 int 型)

int abs(int x)//返回 x 的绝对值

int rand(void)//返回一个范围在 0 到 RAND_MAX 之间的伪随机数

void *malloc(size_t size)//分配所需的内存空间,并返回一个指向它的指针

7.其它

#include <errno.h>;<float.h>;<locale.h>;<math.h>;<setjmp.h>;<signal.h>;<stdarg.h>;<stddef.h>;<time.h>

11.2  C案列库

排序算法

冒泡排序;选择排序;插入排序;希尔排序;归并排序;快速排序

总结

要想成为C语言的高手,需要熟练掌握C语法中的细节!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值