c语言入门教程

135 阅读16分钟

参考教程: www.runoob.com/cprogrammin…

基本语法

www.runoob.com/cprogrammin…

auto声明自动变量
register声明寄存器变量
signed声明有符号类型变量或函数
sizeof计算数据类型或变量长度(即所占字节数)
union声明共用体类型
volatile说明变量在程序执行中可被隐含地改变

C99 新增关键字

_Bool_Complex_Imaginaryinlinerestrict

C11 新增关键字

_Alignas_Alignof_Atomic_Generic_Noreturn
_Static_assert_Thread_local

C 数据类型

printf("int 存储大小 : %lu \n", sizeof(int));

float4 字节1.2E-38 到 3.4E+386 位有效位
double8 字节2.3E-308 到 1.7E+30815 位有效位
long double16 字节3.4E-4932 到 1.1E+493219 位有效位

类型转换

类型转换是将一个数据类型的值转换为另一种数据类型的值。

C 语言中有两种类型转换:

  • 隐式类型转换: 隐式类型转换是在表达式中自动发生的,无需进行任何明确的指令或函数调用。它通常是将一种较小的类型自动转换为较大的类型,例如,将int类型转换为long类型或float类型转换为double类型。隐式类型转换也可能会导致数据精度丢失或数据截断。
  • 显式类型转换: 显式类型转换需要使用强制类型转换运算符(type casting operator),它可以将一个数据类型的值强制转换为另一种数据类型的值。强制类型转换可以使程序员在必要时对数据类型进行更精确的控制,但也可能会导致数据丢失或截断。

C 变量

extern int i; //声明,不是定义
int i; //声明,也是定义

C 变量

以下是各种类型的整数常量的实例:

85         /* 十进制 */
0213       /* 八进制 */
0x4b       /* 十六进制 */
30         /* 整数 */
30u        /* 无符号整数 */
30l        /* 长整数 */
30ul       /* 无符号长整数 */

C 存储类

auto 存储类是所有局部变量默认的存储类。

extern 存储类

extern 存储类用于定义在其他文件中声明的全局变量或函数。

C 运算符

&&称为逻辑与运算符。如果两个操作数都非零,则条件为真。(A && B) 为假。
<<=左移且赋值运算符C <<= 2 等同于 C = C << 2
>>=右移且赋值运算符C >>= 2 等同于 C = C >> 2
&=按位与且赋值运算符C &= 2 等同于 C = C & 2
^=按位异或且赋值运算符C ^= 2 等同于 C = C ^ 2
=按位或且赋值运算符C= 2 等同于 C = C2

杂项运算符 ↦ sizeof & 三元

下表列出了 C 语言支持的其他一些重要的运算符,包括 sizeof 和  ? :

运算符描述实例
sizeof()返回变量的大小。sizeof(a) 将返回 4,其中 a 是整数。
&返回变量的地址。&a; 将给出变量的实际地址。
*指向一个变量。*a; 将指向一个变量。
? :条件表达式如果条件为真 ? 则值为 X : 否则值为 Y

C 判断

switch(表达式)
{
    case 常量表达式1:语句1;
    case 常量表达式2:语句2;
    ...
    default:语句n+1;
}

C 循环

C 提供了下列的循环控制语句。点击链接查看每个语句的细节。

控制语句描述
break 语句终止循环或 switch 语句,程序流将继续执行紧接着循环或 switch 的下一条语句。
continue 语句告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。
goto 语句将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。

C 函数

int max(int num1, int num2);

函数参数

如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数

形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。

当调用函数时,有两种向函数传递参数的方式:

调用类型描述
传值调用该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。
引用调用通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。

默认情况下,C 使用传值调用来传递参数。一般来说,这意味着函数内的代码不能改变用于调用函数的实际参数。


#include <stdio.h> /* 函数声明 */ 
int max(int num1, int num2);
int main () { 
    /* 局部变量定义 */ 
    int a = 100; 
    int b = 200; 
    int ret; /* 调用函数来获取最大值 */ 
    ret = max(a, b); 
    printf( "Max value is : %d\n", ret ); 
    return 0; 
} 
/* 函数返回两个数中较大的那个数 */ int max(int num1, int num2) { /* 局部变量声明 */
    int result; 
    if (num1 > num2) 
    result = num1; 
    else
    result = num2;
    return result; 
}

C 作用域规则

任何一种编程中,作用域是程序中定义的变量所存在的区域,超过该区域变量就不能被访问。C 语言中有三个地方可以声明变量:

  1. 在函数或块内部的局部变量
  2. 在所有函数外部的全局变量
  3. 形式参数的函数参数定义中

C 数组

double balance[10];
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};

获取数组长度

数组长度可以使用 sizeof 运算符来获取数组的长度,例如:

int numbers[] = {1, 2, 3, 4, 5};
int length = sizeof(numbers) / sizeof(numbers[0]);

C enum(枚举)

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};

没有指定值的枚举元素,其值为前一元素加 1。也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5

C 指针

还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C 程序员,学习指针是很有必要的。

正如您所知道的,每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,它表示了在内存中的一个地址。

image.png

    int main () { 
    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; 
    }

C 中的 NULL 指针

在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为指针。

NULL 指针是一个定义在标准库中的值为零的常量。请看下面的程序:

实例

#include <stdio.h> int main () { int *ptr = NULL; printf("ptr 的地址是 %p\n", ptr ); return 0; }

当上面的代码被编译和执行时,它会产生下列结果:

ptr 的地址是 0x0

在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。

如需检查一个空指针,您可以使用 if 语句,如下所示:

if(ptr)
/* 如果 p 非空,则完成 */ 
if(!ptr)
/* 如果 p 为空,则完成 */

int p; //p是整型变量 
int* p; //p是整型指针 
int** p; //p是指向「整型指针」的指针 
int p[3]; //p是数组,数组成员是「int」 
int* p[3]; //p是数组,数组成员是「整型指针」 
int (* p)[3]; //p先被 * 修饰,是一个指针,指向一个数组,数组成员是「int」 
int p(int); //p是一个函数;「所指函数」有一个参数,「参数类型」是「int」,「返回值类型」是「int」 
int (* p)(int); //p是一个指针,指向一个函数;函数的「参数类型」是「int」,「返回值类型」是「int」 
int* (* p(int))[3]; //p是一个函数,该函数有一个「int」参数,返回一个指针,指向一个数组,数组里放的是「整型指针」

函数指针

typedef int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型
int main(void)
{ 
/* p 是函数指针 */ 
int (* p)(int, int) = & max; 
// &可以省略 int a, b, c, d; 
printf("请输入三个数字:");
scanf("%d %d %d", & a, & b, & c);
/* 与直接调用函数等价,d = max(max(a, b), c) */ 
d = p(p(a, b), c); 
printf("最大的数字是: %d\n", d); return 0; }

回调函数

populate_array()  将调用 10 次回调函数,并将回调函数的返回值赋值给数组。

实例

#include <stdlib.h> 
#include <stdio.h> 
  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 
   
   main(void) { 
    int myarray[10]; /* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于传入此参数时传入了 int , 而不是函数指针*/ 
    populate_array(myarray, 10, getNextRandomValue); 
    
    for(int i = 0; i < 10; i++) {
        printf("%d ", myarray[i]); 
    } 
    printf("\n"); 
    return 0; 
  }

编译执行,输出结果如下:

16807 282475249 1622650073 984943658 1144108930 470211272 101027544 1457850878 1458777923 2007237709 

C 字符串

char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
序号函数 & 目的
1strcpy(s1, s2); 复制字符串 s2 到字符串 s1。
2strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。
3strlen(s1); 返回字符串 s1 的长度。
4strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
5strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
6strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。

下面的实例使用了上述的一些函数:

strcmp: string compare 

strcat: string catenate 

strcpy: string copy 

strlen: string length 

strlwr: string lowercase 

strupr: string upercase

a'  表示是一个字符, "a"  表示一个字符串相当于 'a'+'\0';

'' 里面只能放一个字符;

"" 里面表示是字符串系统自动会在串末尾补一个 0。

**1、sizeof()  计算字符串的长度,包含末尾的  '\0'

**2、strlen()  计算字符串的长度,不包含字符串末尾的  '\0'

C 结构体

struct Books {
    char title[50]; 
    char author[50]; 
    char subject[100]; 
    int book_id; 
};

访问结构成员

为了访问结构的成员,我们使用成员访问运算符(.) 。成员访问运算符是结构变量名称和我们要访问的结构成员之间的一个句号。您可以使用 struct 关键字来定义结构类型的变量。下面的实例演示了结构的用法:

void printBook( struct Books book ) { printf( "Book title : %s\n", book.title); printf( "Book author : %s\n", book.author); printf( "Book subject : %s\n", book.subject); printf( "Book book_id : %d\n", book.book_id); }

struct Books *struct_pointer;

C 结构体

共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。

C 位域

C 语言的位域(bit-field)是一种特殊的结构体成员,允许我们按位对成员进行定义,指定其占用的位数。

如果程序的结构中包含多个开关的变量,即变量值为 TRUE/FALSE,如下:


struct packed_struct {
unsigned int f1:1; 
unsigned int f2:1;
unsigned int f3:1; 
unsigned int f4:1; 
unsigned int type:4; 
unsigned int my_int:9; 
} 
pack;

C typedef

typedef vs #define

#define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:

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

C 输入 & 输出

标准文件

C 语言把所有的设备都当作文件。所以设备(比如显示器)被处理的方式与文件相同。以下三个文件会在程序执行时自动打开,以便访问键盘和屏幕。

标准文件文件指针设备
标准输入stdin键盘
标准输出stdout屏幕
标准错误stderr您的屏幕

文件指针是访问文件的方## getchar() & putchar() 函数

int getchar(void)  函数从屏幕读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。您可以在循环内使用这个方法,以便从屏幕上读取多个字符。

int putchar(int c)  函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符。您可以在循环内使用这个方法,以便在屏幕上输出多个字符。式,本节将讲解如何从键盘上读取值以及如何把结果输出到屏幕上。

getchar() & putchar() 函数

int getchar(void)  函数从屏幕读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。您可以在循环内使用这个方法,以便从屏幕上读取多个字符。

int putchar(int c)  函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符。您可以在循环内使用这个方法,以便在屏幕上输出多个字符。

请看下面的实例:

scanf() 和 printf() 函数

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

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

C 文件读写

您可以使用 fopen( )  函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE 包含了所有用来控制流的必要的信息。下面是这个函数调用的原型:

模式描述
r打开一个已有的文本文件,允许读取文件。
w打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。
a打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。
r+打开一个文本文件,允许读写文件。
w+打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。
a+打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。
 int fclose( FILE *fp );

写入文件

int fputc( int c, FILE *fp );

C 预处理器

#define  message_for(a, b)  \
    printf(#a " and " #b ": We love you!\n")

宏定义内的标记粘贴运算符(##)会合并两个参数。它允许在宏定义中两个独立的标记被合并为一个标记。例如:

参数化的宏

int square(int x) {
   return x * x;
}

C 头文件

引用头文件的语法

使用预处理指令  #include 可以引用用户和系统头文件。它的形式有以下两种:

#include <file>

这种形式用于引用系统头文件。它在系统目录的标准列表中搜索名为 file 的文件。在编译源代码时,您可以通过 -I 选项把目录前置在该列表前。

#include "file"

这种形式用于引用用户头文件。它在包含当前文件的目录中搜索名为 file 的文件。在编译源代码时,您可以通过 -I 选项把目录前置在该列表前。

只引用一次头文件

#ifndef HEADER_FILE
#define HEADER_FILE

有条件引用

有时需要从多个不同的头文件中选择一个引用到程序中。例如,需要指定在不同的操作系统上使用的配置参数。您可以通过一系列条件来实现这点,如下:

#if SYSTEM_1
   # include "system_1.h"
#elif SYSTEM_2
   # include "system_2.h"
#elif SYSTEM_3
   ...

C 强制类型转换

强制类型转换是把变量从一种类型转换为另一种数据类型。例如,如果您想存储一个 long 类型的值到一个简单的整型中,您需要把 long 类型强制转换为 int 类型。您可以使用强制类型转换运算符来把值显式地从一种类型转换为另一种类型,如下所示:

常用的算术转换

image.png

image.png

C 错误处理

errno、perror() 和 strerror()

errno、perror() 和 strerror()

C 语言提供了 perror()  和 strerror()  函数来显示与 errno 相关的文本消息。

  • perror()  函数显示您传给它的字符串,后跟一个冒号、一个空格和当前 errno 值的文本表示形式。
  • strerror()  函数,返回一个指针,指针指向当前 errno 值的文本表示形式。 通常情况下,程序成功执行完一个操作正常退出的时候会带有值 EXIT_SUCCESS。在这里,EXIT_SUCCESS 是宏,它被定义为 0。

如果程序中存在一种错误情况,当您退出程序时,会带有状态值 EXIT_FAILURE,被定义为 -1。所以,上面的程序可以写成:


main() { 
int dividend = 20;
int divisor = 5; 
int quotient; 
if( divisor == 0){
    fprintf(stderr, "除数为 0 退出运行...\n");        exit(EXIT_FAILURE); 
} 
quotient = dividend / divisor; 
fprintf(stderr, "quotient 变量的值为: %d\n", quotient ); exit(EXIT_SUCCESS); }

C 递归

#include <stdio.h> double 
factorial(unsigned int i) { 
 if(i <= 1) { 
  return 1;
 } 
 return i * factorial(i - 1); 
} 
int main() { 
   int i = 15;
   printf("%d 的阶乘为 %f\n", i, factorial(i));
   return 0; 
 }

C 可变参数


double average(int num,...) 
{ 
    va_list valist; 
    double sum = 0.0; 
    int i;
    /* 为 num 个参数初始化 valist */ 
    va_start(valist, num); 
    /* 访问所有赋给 valist 的参数 */
    for (i = 0; i < num; i++) { 
    sum += va_arg(valist, int); 
    } 
    /* 清理为 valist 保留的内存 */ 
    va_end(valist);
    return sum/num;
}

C 内存管理

序号函数和描述
1void calloc(int num, int size); 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 numsize 个字节长度的内存空间,并且每个字节的值都是 0。
2*void free(void address); 该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。
3*void malloc(int num); 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
4**void realloc(void address, int newsize); 该函数重新分配内存,把内存扩展到 newsize

注意: void * 类型表示未确定类型的指针。C、C++ 规定 void * 类型可以通过类型转换强制转换为任何其它类型的指针。

重新调整内存的大小和释放内存

/* 动态分配内存 */ 
description = (char *)malloc( 30 * sizeof(char) ); 
if( description == NULL ) { 
      fprintf(stderr, "Error - unable to allocate required memory\n"); 
  } else {
      strcpy( description, "Zara ali a DPS student."); 
  } /* 假设您想要存储更大的描述信息 */ 
  description = (char *) realloc( description, 100 * sizeof(char) ); 
  if( description == NULL ) {
      fprintf(stderr, "Error - unable to allocate required memory\n"); 
  } else {
      strcat( description, "She is in class 10th"); 
  }

C 语言中常用的内存管理函数和运算符

  • malloc() 函数:用于动态分配内存。它接受一个参数,即需要分配的内存大小(以字节为单位),并返回一个指向分配内存的指针。
  • free() 函数:用于释放先前分配的内存。它接受一个指向要释放内存的指针作为参数,并将该内存标记为未使用状态。
  • calloc() 函数:用于动态分配内存,并将其初始化为零。它接受两个参数,即需要分配的内存块数和每个内存块的大小(以字节为单位),并返回一个指向分配内存的指针。
  • realloc() 函数:用于重新分配内存。它接受两个参数,即一个先前分配的指针和一个新的内存大小,然后尝试重新调整先前分配的内存块的大小。如果调整成功,它将返回一个指向重新分配内存的指针,否则返回一个空指针。
  • sizeof 运算符:用于获取数据类型或变量的大小(以字节为单位)。
  • 指针运算符:用于获取指针所指向的内存地址或变量的值。
  • & 运算符:用于获取变量的内存地址。
  • * 运算符:用于获取指针所指向的变量的值。
  • -> 运算符:用于指针访问结构体成员,语法为 pointer->member,等价于 (*pointer).member。
  • memcpy() 函数:用于从源内存区域复制数据到目标内存区域。它接受三个参数,即目标内存区域的指针、源内存区域的指针和要复制的数据大小(以字节为单位)。
  • memmove() 函数:类似于 memcpy() 函数,但它可以处理重叠的内存区域。它接受三个参数,即目标内存区域的指针、源内存区域的指针和要复制的数据大小(以字节为单位)。

C 命令行参数

argc 是指传入参数的个数,argv[]  是一个指针数组,指向传递给程序的每个参数。下面是一个简单的实例,检查命令行是否有提供参数,并根据参数执行相应的动作:

int main( int argc, char *argv[] )  
{
   if( argc == 2 )
   {
      printf("The argument supplied is %s\n", argv[1]);
   }
   else if( argc > 2 )
   {
      printf("Too many arguments supplied.\n");
   }
   else
   {
      printf("One argument expected.\n");
   }
}