前语

  1. 本文首要阅读《C Programing:A Modern Approch》2th ,中的一些章节,做一些模型结构笔记,便于参看。
  2. 本文对内容结构有所合并表述。Github仓库中按章节有更详细的片段代码实践,按需运用。
  3. 辅助参看材料包括但不限于如GUN的C文档,和其他网络文档片段。

一、基本内容

1.简略程序一般内容

一个简略的C程序包括:指令函数句子显现注释

  /*
   * 指令:#include "stdio.h"
   * 函数:int main(){return 0;}
   * 句子:printf("this is a statements\n");
   * 显现:printf("xxx content");
   * 注释:如本段
   * */
#include <stdio.h>
#define PI 3.1415926
int main() {
  printf("this is a C program");
  return 0;
}

2.编译运转程序

预处理->编译->链接
gcc/cc -o <outputfile> <sourcefile> [-w]

预处理:程序交给预处理器,履行#最初的指令
编译:编译器compiler(如:gcc、cc)将程序翻译成机器指令(方针代码)
链接:linker把发生的方针代码和其他附加代码(如一些库函数printf)整合,发生可履行程序

3.变量和赋值

概念 语法表述
类型 short/int/long/float/double/char
声明 int var_name;
赋值 var_name=8;
显现变量 printf("var_name:%d",var_name);
初始化 int h=12,w=13,l=15;
显现表达式的值 printf("%d",h+w+l);
读入输入 scanf("%d",&i);
界说常量名字 #define INC_PER_POUND 16 #define REC_PI (1.0f / 3.14159f)

二、格局化输入输出

1. printf函数和scanf函数

概念 语法 额定点
printf() printf(string,expr1,expr2,...); string或许包括一般字符转化阐明%最初来表明填充值得占位符
scanf() scanf工作法scanf("%d%d%f%f",&i,&j,&x,&y); 寻觅开端方位时,会疏忽空白字符(空格、水平和笔直制表符、换页符、换行符)来表明填充值得占位符

#include <stdio.h>
int main(){
    char sign = 'A';
    int age = 18;
    printf("class is-> %c,age is->%d,next age is->%d\n", sign, age, age + 1);
    int a,b;
    printf("input int number a+b:\n");
    scanf("%d+%d",&a,&b);
    printf("scanf value is a=%d b=%d sum=%d\n",a,b,a+b);
}

2. 转化阐明符(Conversion specification)

%[flag][width][.precision]type
flag:+右对齐 -左对齐
width:最小宽度
precision:精度

# The GNU C Library/Output-Conversion-Syntax

常见转化阐明符

转化符 意义 阐明
%c 单字符
Single character
%d 有符号十进制
Signed decimal integer (int)
%i 有符号十进制
Signed decimal integer (int)
用于printf时和%d体现一致
用于scanf时分会有区别
%u 无符号十进制
Unsigned decimal integer (int)
%e(%E) 指数计数法浮点型
Signed floating-point value in E notation
%f 定点十进制浮点型
Signed floating-point value (float)
%g(%G) 按巨细决议一般或指数计数法浮点型
Signed value in %e or %f format, whichever is shorter
不显无意义的零”0″
%o 将整数输出为无符号八进制
Unsigned octal (base 8) integer (int)
%p 指针类型
Pointer value
%s 字符串文本
String of text
%x(%X) 将整数打印为无符号十六进制
Unsigned hexadecimal (base 16) integer (int)
%% 打印百分号%字符
(percent character)

C99中%zu输出size_t型,打印sizeof()函数值

3. 转义序列(Escape sequence)

常见转义序列符

转义序列 意义
\ \
\a 警报符
\b 回退符,可插曲前一单位的内容
\f 换页
\n 换行符
\r 回车
\t 水平制表符
\v 笔直制表符

三、表达式

算术运算符

最高优先级:+ -(作为一元运算符) * / %
最低优先级:+ -

  • 一元运算右结合,二元运算左结合
类型 包括内容 结合性
一元运算符 +正号运算符
-负号运算符
二元运算符 +
-
*
/
%

赋值运算

简略赋值复合赋值


  int android, java, python,c;
   /*简略赋值*/
  android = 1;
  java = android;
  c = android + java * 10;
  /*复合赋值*/
  c = android = python = java * 10;
优先级(1最高) 类型名称 符号 结合性
1 后缀自增 value++
1 后缀自减 value--
2 前缀自增 ++value
2 前缀自减 --value
2 一元正号 +
2 一元负号 -
3 乘法类 */%
4 加法类 +,-
5 赋值 =,*=,\=,%=,+=,-=
      下列表达式:
      a = b += c++ -d + --e / -f
      相当于:
      step1: a = b += (c++) -d + --e / -f
      step2: a = b += (c++) -d + (--e) / (-f)
      step3: a = b += (c++) -d + ((--e) / (-f))
      step4: a = b += (((c++) -d) + ((--e) / (-f)))
      step5: a = (b += (((c++) -d) + ((--e) / (-f))))
      step6: (a = (b += (((c++) -d) + ((--e) / (-f)))))

四、挑选句子

逻辑表达式

  1. 联系运算符:>,<,>=,<=
  2. 判等运算符:==,!=
  3. 逻辑运算符:!(一元),&&,||(二元)

if

if(express) statement
if(expr) stat else stat
if(expr) stat else if(expr) stat else stat

swtich

switch (expr){
  case constant-expr:statements
  ...
  case constant-expr:statements
  default:statements
}

五、循环loops

名称 语法 额定弥补
while while (expr) stat 惯用:while(1)无限循环
do while do stat while (expr);
for for(expr1:expr2:expr3) stat 无限循环:for(;;)
break break; 跳出循环
continue continue; 操控在循环内
goto identifier:stat
goto identifier;

举例:

#include <stdio.h>
int main() {
  /**
   * 无线循环while(1)
   */
  long long int unLimit = 0;
  while (1) {
	printf("loop while(1) unLimit %lld\n", unLimit++);
	if (unLimit > 99) {
	  goto Label_While_Break;
	}
  }
Label_While_Break:
  printf("this is goto mark while(1) Label\n");
  /**
   * 无限循环for(;;)
   */
  for(;;){
    printf("loop for(;;) unLimit %lld\n", unLimit++);
    if (unLimit > 200) {
	  goto Label_For_Break;
    }
  }
Label_For_Break:
  printf("this is goto mark for(;;) Label\n");
  for(unLimit;unLimit>0;unLimit--){
    printf("this is for decrease, value = %lld\n",unLimit);
  }
  while (unLimit<10){
    printf("this is while(unLimit<10) loop, value = %lld\n",unLimit);
    unLimit++;
  }
  return 0;
}

六、基本类型

数值类型

整数数值类型 表述 一般读写格局
unsigned short int
简写unsigned short
无符号短整型 %hu
short int
简写short
短整型 %hd
unsigned int 无符号整型 %u
int 整型 %d
unsigned long int
简写unsigned long
无符号长整型 %lu
long int
简写long
长整型 %ld
[unsigned] long long
简写[unsigned] long long
长长整型C99 %llu或%lld
浮点数值类型 表述 一般读写格局
float 单精度 %f , %g , %e
double 双精度 %f , %g , %e 前加小l
long double 扩展浮点数 %f , %g , %e 前加大L

短整数:前面加上h,如h<d\o\u\x>
长整数:前面加上l,如l<d\o\u\x>


short short_v = 2;
unsigned short int u_short_v = 2;
int int_v = 10;
unsigned int u_int_v =19;
long long_v = 1000;
unsigned long u_long_v=1009;
long long long_long_v = 10000;
unsigned long long u_long_long_v = 10009;
float float_v = 1.3f;
double double_v = 0.2345678901;
long double long_double_v = 0.2345678901;
printf("short=%hd\t sizeof(short)=%zu\n", short_v, sizeof(short));
printf("unsigned short=%hu\t sizeof(unsigned short)=%zu\n", u_short_v, sizeof(unsigned short));
printf("int=%d\t sizeof(int)=%zu\n", int_v, sizeof(int));
printf("unsigned int=%u\t sizeof(unsigned int)=%zu\n", u_int_v, sizeof(unsigned int));
printf("long=%ld\t sizeof(long)=%zu\n", long_v, sizeof(long));
printf("unsigned long=%lu\t sizeof(unsigned long)=%zu\n", u_long_v, sizeof(unsigned long));
printf("long long=%lld\t sizeof(long long)=%zu\n", long_long_v, sizeof(long long));
printf("unsigned long long=%llu\t sizeof(unsigned long long)=%zu\n", u_long_long_v, sizeof(unsigned long long));
printf("float(m.p=0.2) =%0.2f\t sizeof(float)=%zu\n", float_v, sizeof(float));
printf("float(m.p=1.2) =%1.2f\t sizeof(float)=%zu\n", float_v, sizeof(float));
printf("float(m.p=.6) =%.6f\t sizeof(float)=%zu\n", float_v, sizeof(float));
printf("float(m.p=.10) =%.10f\t sizeof(float)=%zu\n", float_v, sizeof(float));
printf("float(m.p=.10) =%.10lf\t sizeof(double)=%zu\n", double_v, sizeof(double));
printf("float(m.p=.6) =%.6lf\t sizeof(double)=%zu\n", double_v, sizeof(double));
printf("float(m.p=.6) =%.6Lf\t sizeof(double)=%zu\n", long_double_v, sizeof(long double));

在我的电脑测验输出为:

short=2  sizeof(short)=2
unsigned short=2         sizeof(unsigned short)=2
int=10   sizeof(int)=4
unsigned int=19  sizeof(unsigned int)=4
long=1000        sizeof(long)=4
unsigned long=1009       sizeof(unsigned long)=4
long long=10000  sizeof(long long)=8
unsigned long long=10009         sizeof(unsigned long long)=8
float(m.p=0.2) =1.30     sizeof(float)=4
float(m.p=1.2) =1.30     sizeof(float)=4
float(m.p=.6) =1.300000  sizeof(float)=4
float(m.p=.10) =1.2999999523     sizeof(float)=4
float(m.p=.10) =0.2345678901     sizeof(double)=8
float(m.p=.6) =0.234568  sizeof(double)=8
float(m.p=.6) =0.000000  sizeof(double)=16

字符类型

浮点数值类型 表述 读写办法1 读写办法2
[unsigned] char 不同机器
或许有不同字符集
常用ASCII
输入:scanf("%c",&ch);
输出:printf("%c",ch);

putchar(ch)写入ch中
int ch = getchar()读
获取用户输入更快
不会越过空白字符

惯用法

/*skips rest of line*/
while(getchar()!='\n')
   ;
/*skip blanks*/
while (getchar() == '  ')
   ;

转巨细写

/**办法一:*/
if(‘a’ <= ch && ch <= 'z')
    ch =ch-'a'+'A'
/**办法二:*/
#include <ctype.h>
    iftoupper(ch)='A'

实验程序:


#include <stdio.h>
#include <ctype.h>
int main() {
  /*
   * 2.字符类型char
   * */
  char char_value = 'a';
  printf("char value=%c\n", char_value);
  /**
   * 3.转大写
   */
  if ('a' <= char_value && char_value <= 'z') {
	printf("upper char method normal:%c\n", char_value - 'a' + 'A');
	printf("upper char method(toupper) 2:%c\n", toupper(char_value));
  }
  /**
   * 4.typedef 长处,易了解,修正
   */
  typedef int STATUS;
  STATUS status_a = 100;
  printf("tpyedef status:%d\n", status_a);
  /**
   * 5.读写字符scanf()/printf()
   * 不会越过空白字符
   */
  char scanf_char;
  printf("input a char (by scanf method):\n");
  scanf("%c", &scanf_char);
  printf("out you char value(by printf):%c\t\n", scanf_char);
  while (getchar() != '\n');
  printf("Skip rest of line...\n");
  /**
   * 6.读写字符
   * getChar()/ 读取输入字符,一次只能单字符,不会越过空白字符
   * putChar(ch) 一次只能单字符输出
   *
   */
  printf("\ninput (by getchar() single char ):");
  int get_char = getchar();
  putchar(get_char);
  printf("\n__________________\n");
  printf("output (by getChar()) :%c\n", get_char);
  printf("+++++++++++++++++++++\n");
  printf("++++++input to output++++++\n");
  while ((get_char = getchar()) != EOF) {
	putchar(get_char);
  }
  return 0;
}

类型转化

中间的unsigned 全名为unsigned long 类型

语言篇—现代C记录

  • 假如变量类型至少和表达式类型相同“宽”,在赋值过程中转化:
    char c;int i; float f;double d;
    i=c; f=i; d=f;
  • 强制转化:
    cast expressing:(type-name)expression

sizeof运算

sizeof expressing: sizeof(type-name)

printf("size of int:%lu\n",(unsigned long) sizeof(int));
printf("size of int:%zu\n", sizeof(int));//c99 only

七、数组

#define N 10
#define M 11
int main(){
    /**
     * 1.一维数组
     * 界说:int a[N];
     * 初始化:int a[10]={1,2,3,4}
     *        或者 a[10]={0}
     * c99: int a[10]={[1]=9,[5]=7};指定方位初始化
     * 一起存在 int a[]={1,2,9,4,[0]=5,8} 输出 {5,8,9,4}
     */
    /**
     * 2.多维数组
     * 界说:int a[M][N];
     * 初始化:int a[M][N] = {{1,2,3},{1,23,4},...};可不全列出
     * C99(可指定方位初始化): int a[2][2] = { [0][1]=1,[1][1]=2};
     */
   /**
    * 3. 常量数组 constant arrays
    * 界说:在数组前面加 const
    */
  const char hex_chars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
  /**
   ** 4. c99中的变长数组VLA
   * 变长长度不一定用变量来指定,恣意表达式(能够包括运算):
   * int a[3*i+5];
   * int b[j+k];
   * 变长数组首要约束是没有静态存储期限,没有初化式
   */
   int vla[3*N+M];
 }

实验程序:


#include <stdio.h>
#define N 10
#define M 11
/**
 * 带参数宏
 */
#define SIZE(array) ((int)(sizeof(array)/ sizeof(array[0])))
int main() {
  /**
   * 1.一维数组
   */
  unsigned int one_dimensional_array[10] = {12, 34, 1,};
  /*sizeof回来类型为size_t为无符号类型,不强转或许会有正告,因为变量i为有符号整数*/
  for (int i = 0; i < (int) (sizeof(one_dimensional_array) / sizeof(one_dimensional_array[0])); ++i) {
	printf("index of One dimensional[%d]:%d\n", i, one_dimensional_array[i]);
  }
  /*
   * 运用宏界说替换长度核算
   */
  for (int i = 0; i < SIZE(one_dimensional_array); ++i) {
	printf("\tindex of One dimensional[%d]:%d\n", i, one_dimensional_array[i]);
  }
  /**
   * 8.2.1多维数组
   */
  int multi_dimensional_array[M][N] = {{1, 2, 3}, {4, 5, 6}};
  printf("multi dimensional array [%dx%d] value:\n", M, N);
  for (int i = 0; i < M; ++i) {
	printf("\n");
	for (int j = 0; j < N; ++j) {
	  printf("index[%d][%d] = %d  ", i, j, multi_dimensional_array[i][j]);
	}
  }
  /**
   * 8.2.2 常量数组 constant arrays
   * 界说:在数组前面加 const
   */
  const char hex_chars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
  for (int i = 0; i < SIZE(hex_chars); ++i) {
	printf("\n constant hex char [%d] = %c", i, hex_chars[i]);
  }
  /**
   ** 8.3 c99中的变长数组VLA
   * 变长长度不一定用变量来指定,恣意表达式(能够包括运算):
   * int a[3*i+5];
   * 变长数组首要约束是没有静态存储期限,没有初化式
   */
  int vla[3 * N + M];
  for (int i = 0; i < SIZE(vla); ++i) {
	printf("\t VLA[%d]:%d\n", i, vla[i]);
  }
  return 0;
}

八、函数

界说语法

return-type  function-name (parameters){
    declarations
    statements
}

声明语法

函数声明形参名也可省掉:
int sum(int a[],int size); 可变为: int sum(int [],int );

return-type  function-name (parameters)

实践参数

语言篇—现代C记录过程 描绘
值传递 实践参数是经过值传递:调用函数时,核算出每个实践参数的值,并且把他赋值给相应的形式参数。在履行过程中,对形式参数的改动不会影响实践参数的值。因为形式参数中包括的是实践参数值的副本
转化 C答应实践参数与形式参数类型不匹配情况下函数调用,履行实践参数的提升转化
数组参数 描绘
数组型实践参数 一维不必阐明数组长度,数组长度需作为参数传递进来
多维时只可不阐明榜首维长度
int sum(int a[],int length)
int sum(int a[][M],int length)
变长数组型形式参数 运用变长数组形式参数时,参数次序很重要
int sum(int n,int a[n])
int sum(int n,int a[*])
数组参数运用static 表明最少长度,多维时只效果榜首维
int sum(int [static 10], int)

九、程序结构

类型 生命 效果域
局部变量
(函数体内声明的变量)
主动存储期限,函数调用时主动分配,回来时收回 块效果域: 从变量声明开端点,到函数体的结束
静态局部变量
(加static关键字的元素)
静态存储期限,拥有永久的存储单元。整个程序履行期间都会保存变量值 1.拥有块效果域
2.对其他函数不行见,同一函数再调用,保存这些数据。
形式参数 主动存储期限,在每次函数调用时主动初始化(调用中经过赋值取得相应实践参数的值) 拥有块效果域
外部变量(全局变量) 静态存储期限,外部变量的值将永久保存 拥有文件效果域 : 从变量声明的点开端到文件结束

十、指针

指针变量

在核算机中,指针取值规模或许不同于整数取值规模,能够用指针p存储变量i的地址,也便是说指针便是地址,指针变量便是存储地址得变量

语言篇—现代C记录

取址运算符、直接寻址运算符、指针赋值

int main(){
    int i=0,*p;//声明指针变量p
    p=&i;// & 取址运算符,p指向i,把i的地址赋值给了指针p
    int k=1,*q=&k;//合并写法
    printf("%d",*p);// *(直接寻址运算符)拜访存储在目标中的内容 ,也可幻想成&的逆运算
}
int main(){
    int i=0,*m,*n;//声明指针变量m,n
    m=&i;// i地址仿制给m
    n=m;// m的内容仿制给n
    *n=2;// 此处i的值变为2
    printf("%d",*p);// *(直接寻址运算符)拜访存储在目标中的内容 
}

指针作为参数

重要效果

  • 函数调用中用作实践参数的变量无法改动,当希望函数能够改动变量时, 指针作为参数就能解决此问题。
  • 指针传参高效原因是,假如变量需求大量存储空间,传递变量的值有时会糟蹋时间和空间。
void decompose(double, long *, double *);
void decompose(double x, long *int_part, double *frac_part) {
  *int_part = (long) x;
  *frac_part = x - *int_part;
}
int main{
    double pi = 3.1415926;
    long int_part;
    double frac_part;
    // 函数用指针作为参数,改动变量的值
    decompose(pi, &int_part, &frac_part);
    printf("pi int_part=%lu, frac_part=%f\n", int_part, frac_part);
}

const维护指针参数

有时分咱们仅需求查看参数的值,而不想改动他的值也有或许。const关键字维护参数,如void fruit(const int *price)

指针作为回来值

/**
 * 指针作为回来值
 * @return
 */
int *find_middle(int n, int a[n]) {
  return &a[n / 2];
}
int *find_max(int *a, int *b) {
  if (*a > *b) {
   return a;
  } else {
   return b;
  }
}

十一、指针和数组

1.指针算术运算

  • 加,减,
  • 指针相减为间隔
  • 指针比较取决于在数组中的方位
  • 复合常量指针(不必先申明一个数组变量)
int main() {
  int a[] = {1, 2, 3, 5, 6, 7, 8, 9, 10};
  int *p = &a[0], *q = &a[6];
  //1. 加整数
  p += 4;
  printf("p %d\n", *p);
  //2. 减整数
  p -= 3;
  printf("p %d\n", *p);
  //3. 指针相减
  printf("q-p = %lld\n", q - p);
  //4. 指针比较(取决于在数组中的方位)
  printf("q>p:%d q<p:%d\n", q > p, q < p);
  //5. 复合常量的指针
  int *k = (int[]) {2, 4, 6, 8, 10};
  k += 4;
  printf("*K=%d\n", *k);
  return 0;
}

2.指针用于数组处理

#define N 10
int main() {
  // Q&A 有些编译器来说,实践上依靠下标的循环发生更好的代码
  int a[] = {1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, *p;
  for (p = &a[0]; p < &a[N]; p++) {
   printf("%d\n", *p);
  }
  return 0;
}
表达式 意义
*p++或*(p++) 自增前表达式的值是*p,今后再自增p
*(p)++ 自增前表达式的值是*p,今后再自增*p
++p或(++p) 先自增p,自增后的表达式的值是*p
++*p或++(*p) 先自增p,自增后的表达式的值是p

3.数组名作为指针

用数组名作为指针

能够使数组遍历变得愈加简练,核心体现在下面代码a+N部分

#define N 10
int main() {
  // 数组名作为指针
  int a[] = {1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, *p,*q;
  // 原始写法
  for (q = &a[0]; q < &a[N]; q++) {
   printf("*q=%d ", *q);
  }
  int sum = 0;
  // 惯用法(更简练)
  for (p = a; p < a + N; p++) {//此处a+N了解为,将a当作指针,然后做了指针的加法运算
   sum += *p;
   printf("\n sum = %d *p=%d", sum,*p);
  }
  return 0;
}

用指针作为数组名

#define N 10
int main() {
  // 用指针作为数组名
  int a[] = {1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, *p = a, *q = &a[0], *k;
  k = a;
  // 三种效果相同
  for (int i = 0; i < N; ++i) {
   printf("p[%d]=%d\n", i, p[i]);
   printf("q[%d]=%d\n", i, q[i]);
   printf("k[%d]=%d\n", i, k[i]);
  }
  return 0;
}

4、指针和多维数组

数组指针和指针数组

int (*p)[n];//数组指针
int *p[n];//指针数组

名词了解:

方便区别中间加上“的”字

数组的指针:是一个指针,什么样的指针呢?指向数组的指针。
指针的数组:是一个数组,什么样的数组呢?装着指针的数组。

了解办法:

形容词+首要名词

step1:由优先级()>[]>*确认首要名词

step2: 由剩下部分确认形容词

语言篇—现代C记录

二维数组的队伍

#include <stdio.h>
#define N 10
#define M 10
int main() {
  // 用指针作为数组名
  int a[M][N] = {{1, 2, 3, 5, 6, 7, 8, 9, 10, 11},};
  printf("eg1:运用指针打印数组 \n");
  int *p;
  // 1.按在存储上的特点,当成一维数组(Q&A:合法可是可读性差,在现代编译器上的速度优势很少乃至没有)
  for (p = &a[0][0]; p < &a[M - 1][N - 1]; p++) {
   printf("%d ", *p);
  }
  printf("\neg2:运用指针更改行\n");
  // 2.运用指针更改第m行
  int *row, m = 2;
  for (row = a[m]; row < a[m] + N; row++) {
   *row = 20;
  }
  /* 数组指针vs指针数组
   * Tips:
   * 优先级:()>[]> *
   * 数组的指针:int (*y)[N]为数组指针,可了解为 int(*y)[N]是个含有N个元素的数组类型指针
   * 协助记忆:step1.一个一般指针 int *y ,依据优先级,(*p)是一个指针,详细是个xxx的指针
   *         step2.一个数组类型指针需求后边加上[],变为数组类型指针,因为优先级联系,需求将用括号()将 *y 包裹起来,变为int (*y)[]
   *         step3.在step2上没办法确认数组类型指针以此走多少步,所以在[]上参加步长N,最终为 int (*y)[N]
   *
   * 指针的数组:int *y[N]为指针数组,可了解为 N个指针元素的数组
   * 协助记忆:step1.依据优先级,p[]是一个数组,是个xxx的数组
   */
  // 3.运用指针更改第n列
  printf("\neg3:运用指针更改第n列\n");
  int (*y)[N], n = 3;
  for (y = &a[0]; y < &a[M]; y++) {
   (*y)[n] = 30;
  }
  // 4.常见嵌套遍历
  for (int i = 0; i < M; ++i) {
   for (int j = 0; j < N; ++j) {
     printf("a[%d][%d]=%d ", i, j, a[i][j]);
   }
   printf("\n");
  }
  return 0;
}

十二、字符串

1.字符串字面常量

C言语如何存储字符串

c言语将字符字面常量当做字符数组来处理,长度为n的字符串,体系会分配长度为n+1的内存空间

语言篇—现代C记录

字符串变量

声明变量并初始化

char date1[8]="June 14";char date2[9]="June 17";char date3[7]="June 19";

语言篇—现代C记录

字符数组与字符指针

int main() {
  //惯用法,因为结束空字符
#define STR_LENGTH 8
  char name[STR_LENGTH + 1] = "abc";
  char *p_str = "hello world!";
  char c_array[] = {'a', 'b', 'c'};
  char c_array2[] = "abc2";
  char *p;
  p=c_array;//p作为字符串之前,有必要把p指向字符数组
  int size_name = (sizeof(name) / sizeof(name[0]));
  for (int i = 0; i < size_name; ++i) {
   printf("name# %c total=%d\n", name[i], size_name);
  }
  int i = 0;
  while (((int) *p_str) != '\0') {
   printf("p_str#%d %c\n", i++, *(p_str++));
  }
  printf("p_str#end %c\n", *p_str);
  return 0;
}

2.字符串读写

printf()与puts()写(打印)函数

函数 体现
puts() 写完后主动增加额定的换行符,从而移动到下一行开端处
printf() 1.逐一写字符串中的字符,直到空字符中止

scanf()与gets()读(输入)函数

函数 体现
gets() 1.能够读入一阵行
2.不会在开端读字符串前越过空白字符
3.继续读入到换行符中止,此外会疏忽换行符,不会存储到数组用,用空字符代替换行符
scanf() 1.读入字符串永远不包括空白字符
2.换行符、空格、制表符会使它中止读入,因而通常读不了一整行
#include <stdio.h>
#include <string.h>
#define  NEXT "(y/n)"
#define  YES "y"
#define  NO "n"
int main() {
  char str[] = "we have fun yet!";
  char *str2 = "we have fun2 yet! 酷";
  char str3[3] = "abc";
  //1.字符串写
  printf("%.6s\n", str);
  printf("%s\n", str2);
  printf("%s\n", str3);
  //2.字符串读
  char str_a[3] = "abc";
  /*
   *运用scanf()办法
   */
  while (1) {
   printf("input by scanf():\n");
   scanf("%s", str_a);
   puts(str_a);
   int size_s_str = (sizeof(str_a) / sizeof(str_a[0]));
   printf("s_a size=%d \n", size_s_str);//本机测验输入超越3任然能显现(内存越界或许发生不可思议的成果)
   printf("从头输入%s?:\n", NEXT);
   scanf("%s", str_a);
   if (strcmp(str_a, NO) == 0) {
     break;
   }
  }
  getchar();
  /*
   *运用gets()办法
   */
  while (1) {
   printf("input by gets():\n");
   gets(str_a);
   puts(str_a);
   int size_s_str = (sizeof(str_a) / sizeof(str_a[0]));
   printf("s_a size=%d \n", size_s_str);//本机测验输入超越3任然能显现(内存越界或许发生不可思议的成果)
   printf("从头输入%s?:\n", NEXT);
   gets(str_a);
   if (strcmp(str_a, NO) == 0) {
     break;
   }
  }
  return 0;
}

逐字符读入字符串&自界说read_line()

考虑的问题:

  • 存储前是否该越过空白字符
  • 什么字符导致函数中止读取,存储还是疏忽这类字符
  • 字串太长无法存储,怎样处理剩下字符,丢掉还留给下一次输入

举例:界说一个不越过空白,在换行符中止(不存储),疏忽额定字符的read_line()函数

int read_line(char[], int);
int read_line(char str[], int n) {
  int ch, i = 0;
  while ((ch = getchar()) != '\n') {
   if (i < n) {
     str[i++] = ch;
   }
  }
  str[i] = '\0'; //手动加字符结束
  return i;
}
int main() {
  char str[] = "we have fun yet!";
  int length = read_line(str, 4);
  puts(str);
  printf("length=%d",length);
  return 0;
}

举例2:从规范输入中读取字符,直到遇到换行符 '\n' 停止,函数回来为输入的字符内容

typedef char *String;
String read_line() {
  String s1 = NULL;
  int size = 0, capacity = 1;
  s1 = (String) malloc(capacity * sizeof(char)); // 分配内存
  if (s1 == NULL) {
   printf("过错:内存分配失利。\n");
   exit(EXIT_FAILURE);
  }
  char c;
  while ((c = getchar()) != '\n') {
   s1[size++] = c;
   if (size == capacity) { // 假如需求,增加容量
     capacity *= 2;
     s1 = (String) realloc(s1, capacity * sizeof(char));
     if (s1 == NULL) {
      printf("过错:内存分配失利。\n");
      exit(EXIT_FAILURE);
     }
   }
  }
  s1[size] = '\0'; // 增加空字符
  return s1;
}

C言语中常见字符串库

<string.h> 中有许多操作字符串的函数,下面列举少数几种

函数名 原型 描绘
语言篇—现代C记录strcpy char *strcpy(char *s1,const char *s2) 1. 仿制s2到s1,直到遇见s2榜首个空字符(此空字符也仿制)
2. 不改动s2,函数回来s1指针
strlen size_t *strlen(const char *s) 回来数组中字符串长度
strcat char *strcat(char *s1,const char *s2) s2内容(含'\0')追加到s1结束,回来s1(指针)
strncat char *strcat(char *s1,const char *s2,int n) 1.s2追加到是结束,遇空字符停止
2.n为待仿制字符数(不包括'\0'空字符)
3.更安全
strcmp int *strcmp(const char *s1,const char *s2,int n) 利用字典次序比较
空格符<数字<大写<小写字母

语言篇—现代C记录
示例程序

#include <stdio.h>
#include <string.h>
#define MAX_REMINDER_SIZE 60
#define MSG_SIZE 60
int read_line(char *, int);
int read_line(char s[], int n) {
  int count = 0, ch;
  while ((ch = getchar()) != '\n') {
   if (count < n)
     s[count++] = ch;
  }
  s[count] = '\0';
  return count;
}
/*
 *
 * 月提醒列表:
 * 按日期排序,输入0打印,打印格局如下
 *      day reminder
 *        5 task1
 *        9 task2
 *       15 task3
 *       25 task4
 */
int main() {
  //1.按日期从小到大提示 day msg,其间day右对其,msg左对其
  //2.超越MAX_REMINDER_SIZE,不能录入
  //3.输入0时分显现一切内容
  char reminders[MAX_REMINDER_SIZE][MSG_SIZE + 3];//保存
  char day_str[3], remind_msg[MSG_SIZE + 1];//日期字串 音讯字串
  int day_number = 0, msg_count = 0;
  char s1[10] = "abc", s2[10] = "xyz";
  strcpy(s1, s2);//xyz
  strcat(s1, s2);//xyzxyz
  printf("%lld", strlen(s2));
  for (;;) {
   if (msg_count > MAX_REMINDER_SIZE) {
     puts("+++++++++++ No space left++++++++");
     break;
   }
   printf("Enter a day and reminder:");
   scanf("%2d", &day_number);
   if (day_number == 0) {
     break;
   }
   //读入前两位数字并保存到
   sprintf(day_str, "%2d", day_number);
   //读取指定长度字符
   read_line(remind_msg, MSG_SIZE);
   //按日期小到大排
   int i = 0, j = 0;
   //从0行开端,找到按日期巨细排的方位i
   for (i = 0; i < msg_count; i++) {
     if (strcmp(day_str, reminders[i]) < 0)
      break;
   }
   // 假如i的方位是小于当时msg总数,则将此条msg刺进i行,i行后边的往后挪一行
   for (j = msg_count; j > i; j--) {
     strcpy(reminders[j], reminders[j - 1]);//将j-1行挪到j行
   }
   //仿制日期到行
   strcpy(reminders[i], day_str);
   //拼接msg到后边
   strcat(reminders[i], remind_msg);
   msg_count++;
  }
  printf("day reminder\n");
  for (int i = 0; i < msg_count; ++i) {
   printf(" %s\n", reminders[i]);
  }
  return 0;
}

字符串数组

存储字符串数组最佳的办法:

  • 最显着办法:数组式,缺陷比较糟蹋空间
  • 省空间办法:指针式
/*
 * 存储字符串数组最佳办法
 */
int main() {
  //办法一:数组式(废空间)
  char plant_a[][8] = {"Mercury", "Venus", "Earth",
                       "Mars", "Jupiter", "Saturn",
                       "Uranus", "Neptune", "Pluto"};
  //办法二:指针式
  char *pant_b = {"Mercury", "Venus", "Earth",
                  "Mars", "Jupiter", "Saturn",
                  "Uranus", "Neptune", "Pluto"};
  return 0;
}

指令行参数

/*
 * 指令行参数
 * 如:ls -l count.c
 * arg[0]程序名 剩下为余下指令参数,最终附加一个空指针argv[argc]=NULL
 */
int main(int argc,char* argv[]) {
  for (int i = 0; i <= argc; ++i) {
    printf("%d=%s\n",i,argv[i]);
  }
  char **p;
  for (p=&argv[0];*p!=NULL;p++){
    printf("%s\n",*p);
  }
  return 0;
}

十三、预处理器

1.预处理器原理

语言篇—现代C记录

gcc <SourceFile> -E 能够看到预处理器的输出

注意: 预处理器仅知道少数C言语的规矩。因而在履行指令时是有或许发生不合法程序,有时看起来正常但过错找起来难,能够查看一下预处理输出是一种办法

2.预处理指令

特征:

  1. 指令都以#开端
  2. 指令的符号间能够刺进恣意数量空格或水平制表符
  3. 指令总在榜首个换行符出结束,除非运用 \ 符参加当时行结束,明确地指明要延续
  4. 指令能够出现在程序的任何地方
  5. 注释能够和指令放在同一行

部分预处理指令:

预处理指令领域 指令
宏界说 #define 指令界说一个宏
#undef志宁删去一个宏
条件编译 #if#ifdef#ifndef#elif#else#endif 以测验条件来确认程序是否包括一段文本块
文件包括 #include 指定的文件内容被包括到程序中
其他特别指令 1. #error显现犯错音讯
2. #line 改动程序行编号办法
3. #pragam 为编译器履行某些特别操作特供一种办法

3.宏界说

标题 描绘 例子
简略宏 #define 标识符 替换列表
参数宏 #define 标识符(x1,x2,…,XN) 替换列表
#运算符 将宏的一个参数字符串化,只答应出现在参数宏的替换列表
##运算符 将两个记号(如标识符)粘合在一起,变成一个记号
预界说宏 一种已经界说好的宏,每一个都是整数常量或字符串字面量 __DATE__,__TIME__,__STDC__,__FILE__,__LINE__
空宏参数 答应宏调用时恣意参数为空,首要出现在参数宏或#运算符或##运算符的调用中
参数个数可变宏 在宏界说的参数列表最终中运用...,…省掉号在,__VA_ARGS__专用标识符,代表与...对应的参数

特别__func__标识符与预处理器无关,相当于当时履行函数都的函数名字符串变量

#include <stdio.h>
/*
 * 1. 简略宏
 */
#define N 10
#define D "=%d\n"
/*
 * 2.参数宏
 */
#define IS_EVEN(n) ((n)%2==0)
/*
 * 3. #运算符,用于参数宏的替换列表中,字符串化
 */
#define P_INT(x) printf(#x D,x)
/*
 * 4. ##运算符,粘合两个记号,将两个记号变为一个记号
 */
#define M_K(n) i##n
#define JOIN(x,y,z) x##y##z
int main(){
IS_EVEN(3);
int a=3,b=2;
P_INT(a-b);
int M_K(1)=1,M_K(2)=2;// 相当于声明i1,i2
P_INT(i1-i2);
/*
 * 5. 预界说宏,整数常量或字符串字面量
 */
  puts(__DATE__);
  puts(__TIME__);
  printf("%d",__STDC__);
  printf("%s %d",__FILE__,__LINE__);
  /*
  * 6. 空宏参数
  */
  int M_K()=0;
  P_INT(i);
  int JOIN(r,,),JOIN(r,s,t),JOIN(r,,t);
  r=1,rst=2,rt=3;
  /*
   * 7 参数个数可变的宏,...省掉号在参数列表最终,__VA_ARGS__专用标识符,代表与...对应的参数
   *
   */
#define TEST(condition,...) ((condition)? \
              (printf("test passed:%s\n",#condition)): \
              (printf(__VA_ARGS__)))
  TEST(3>2,"3>2 %s","test");
  TEST(2>3,"output %d is not big than> %d\n",2,3);
  /*
   * 8. __func__标识符,与预处理器无关,每函数都可拜访
   */
  printf("%s return",__func__ );
#undef N
  return 0;
}

十四、 构建大型程序

多文件程序的文件

标题
源文件 .c结束的全部文件
头文件 常规.h结束的文件

#include 包括规矩

标题
#include <文件名> 搜索体系头文件
#include "文件名" 搜索当时目录头文件,后体系文件目录
#include 记号 #include CPUFILE,CPUFILE是一个依据不同体系下不同架构的宏界说

头文件中的一般其他内容

标题
同享宏界说 #define TRUE 1
同享类型界说 typedef int BOOL
同享函数界说 函数原型声明
同享变量声明 变量声明和界说int i,编译器会分配变量内存空间
变量声明extern int i ,extern编译器不必每次编译文件时为i分配变量内存空间
维护头文件 多文件归根都引证了同一个头文件,用#ifndef#endif指令封闭文件内容
#error指令 经常放在头文件中,用来查看不包括头文件的条件
// 举例一个头文件包括内容
#ifndef PROGRAM_COURSE_H_
#define PROGRAM_COURSE_H_
#define TRUE 1
typedef int BOOLEAN;
int value;
extern int ext_value;
int sum(int,int);
#ifndef __STDC__
#error "This C program requires a Standard C compiler!"
#endif
#endif //PROGRAM_COURSE_H_

十五、 结构&联合&枚举

概述

类型 描绘
结构struct 结构里的每个成员存储在不同地址中
联合union 1.联合里的每个成员存储在同一地址中
2.编译器为最大成员分配满足内存空间
3.任一成员改动值会改动其他成员的值
枚举enum 1. 体系内部将枚举变量和常量看做整数
2. 默许情况下按0.1.2…赋给枚举中的常量
3.能够为枚举常量自由挑选值,在声明时指明数值

命名和用法

两种命名的办法

#include <stdio.h>
/*-----------------------结构----------------------------*/
//省掉了结构符号
struct {
  int version_1;
  char name_1;
} a1;
//(结构)界说办法1:包括了结构符号
struct Part {
  int version_1;
  char name_1;
} b1, b2, b3;//一起声明3个结构体变量,也可不在此处声明
struct Part b4;//声明一变量
//(结构)界说办法2:运用typedef
typedef struct {
  int version_2;
  char name_2;
} Part2;//此处运用类型界说将结构命名为类型Part2
Part2 c1, c2;//声明两个变量
/*-----------------------联合----------------------------*/
//(联合)界说办法1:包括了联合符号
union Union_1 {
  int weight;
  double name;
} d1, d2, d3;//一起声明3个联合变量,也可不在此处声明
//(联合)界说办法2:运用typedef
typedef union {
  int version_2;
  char name_2;
} Union_2;//此处运用类型界说将联合命名为类型Part2,
/*-----------------------枚举----------------------------*/
enum { ONE, TWO, THR } f1;//界说3个枚举常量 和一个变量
enum Enum_1 { FIV, SIX } f2;// 办法一:包括了枚举符号
typedef enum { TRUE=1, FALSE=0  } BOOL;//办法2:运用typedef类型界说

十六、指针高档应用

动态存储分配&空判断&开释

三种内存分配函数(内存块都来至于) 描绘 原型
malloc 分配内存块,但不进行初始化,少一步比calloc更高效 void *malloc(size_t size)
calloc 分配内存块,并进行清零 void *calloc(size_t nmemb,size_t size)
realloc 调整先前分配的内存块 void *realloc(void *ptr,size_t size)
内存开释函数 描绘 原型
free 开释不需求的内存块 void *free(void *ptr)
空指针
NULL 如分配内存时未找到满足巨细,就会回来空指针,if(P=NULL){...}
悬空指针 如在开释p指向的内存后,再拜访或修正开释掉的内存块
其他
->运算符 称为右箭头挑选,用于 node->value代替 *node.value的组合
restrict受限指针 int *restrict p;
1.假如p指向的目标需求修正,则目标不会答应除p之外的任何办法拜访
2.假如一个目标有多种拜访办法,通常将这些办法互称为别号

十八、 声明

语言篇—现代C记录

声明阐明符

存储类型 存储期限 效果域 链接
auto 主动存储期限,不明确指明,块内声明变量默许的 块效果域 无链接
static 静态存储期限 块效果域或文件效果域 块内无链接,块外内部链接
extern 静态存储期限 块效果域或文件效果域 不做阐述
register 存储在寄存器 (CPU存储单元)主动存储期限 块效果域 无链接
  • 存储期限:内存单元的开释和取得
  • 效果域:能够引证变量的那部分程序文本
  • 链接:同享变量的规模,外部链接能够被几个或全部文件同享,内部链接归于单一文件,无连接归于函数独享

类型限定符

const 声明一些相似变量的目标,这些变量时只读的
volatile 用于底层编程中,告诉编译器某些数据是易变的,这种内存空间的值容易在运转期间发生改动,即使程序自身并未企图存放新值
restrict受限指针(c99) int *restrict p;
1.假如p指向的目标需求修正,则目标不会答应除p之外的任何办法拜访
2.假如一个目标有多种拜访办法,通常将这些办法互称为别号
类型阐明符 void,char, short,int,long,float,double,signed,unsigned,结构联合枚举阐明符,typedef创立的类型名

声明符

  • 包括标识符,标识符前面或许有*,后边或许有[],()。3种符号组合能够创立杂乱声明
  • 解说杂乱声明
    • 从内向外
    • 挑选优先级,()> [] > *
  • 能够用类型界说来简化杂乱声明

初始化式

初始化变量

标题 初始化式
静态存储期限的变量 有必要是常量
主动存储期限变量 不需求是常量
花括号中的数组、结构、联合 有必要只含常量表达式
主动类型的结构、联合 能够是别的一个结构和联合

未初始化的变量

未初始化变量有未界说的值,但并不总是这样

  • 主动存储期限变量没有默许初始值。不能预测初始值,每次变量变为有效时值或许不同
  • 静态存储期限变量默许情况初始值为0。

内联函数

  • 额定开支:调用函数和函数回来所发生的工作量。
  • 运用内联函数inline:减少额定开支。依靠编译器,有的编译器能够疏忽这一主张。
    • 函数中不能界说可改动的static变量。c99
    • 函数中不能引证具有内部链接的变量。c99

十九、 程序设计

  1. 模块

    • 聚合性 低耦合高内聚
    • 类型
      数据池 一些相关变量或常量的调集,如<limits.h>
      相关函数的调集
      抽象目标 关于躲藏的数据结构进行操作的函数调集
      抽象数据类型ADT 详细数据完成办法躲藏起来的数据类型称为抽象数据类型
  2. 信息躲藏

    • 标题
      安全性
      灵活性
  3. 抽象数据类型

    • 标题
      封装 其他言语对封装支撑要好一些
      不完整类型 描绘了目标,但缺少界说目标巨细的信息
  4. 抽象数据类型例子 —— 栈运用不同办法完成(详细见示例程序)

    • 运用定长,变长,链表完成,但用同一个头文件

二十、 底层程序设计

  1. 位运算符
标题
<< 左移
>> 右移
~ 按位求反
& 按位求与
按位异或
| 按位或
  1. 结构种的位域

  2. 其他底层技能

二十一、 规范库

标题
C89 15个
<assert.h>
<ctype.h>
<errno.h>
<float.h>
<limits.h>
<local.h>
<math.h>
<setjmp.h>
<signal.h>
<stdarg.h>
<stddef.h>
<stdio.h>
<stdlib.h>
<string.h>
<time.h>
C99 新增9个
<cxomplex.h>
<fenv.h>
<inttypes.h>
<iso646.h>
<stdbool.h>
<stdint.h>
<tgmath.h>
<wchar.h>
<wctype.h>

二十二、 输入、输出

1. 流

标题
文件指针 File *fp1 C中对流的拜访是经过文件指针完成
规范流
文件指针 默许意义
stdin 规范输入 键盘
stdout 规范输出 屏幕
stderr 规范过错 屏幕
重定向
重定向输入:强制程序从文件输入而不是从键盘输入 办法:前面加上字符<,如demo <in.dat
重定向输出:强制程序从文件输出而不是从屏幕输出 办法:前面加上字符>,如demo >out.dat
<stdio.h>支撑的两种类型文件
文本文件 字节表明字符,可查看或编辑文件
二进制文件 字节不一定表明字符,字节组能够表明其他类型数据

2. 文件操作

标题 语言篇—现代C记录
翻开文件 fopen(文件名,形式) 参看形式表
关闭文件 fclose(文件指针) 参看形式
翻开的流附加文件 freopen(文件名,形式,附加文件指针) 附加文件指针一般为规范流或其他
临时文件 1.File *tempfile(void)
2.char *tempnam(char *s)
1. tempfile,易用,缺陷不能命名,按需保存起来
2. tempnam生成一个唯一的、可用于命名临时文件的字符串,你需求手动将其作为参数传递给其他函数(如 fopen())以创立实践的临时文件。请注意,在某些体系上,因为安全性问题而不推荐运用此函数。
文件缓冲 1. fflush(文件指针)
2. void setbuf(文件指针,缓冲数组,<缓冲形式>,>运用缓冲巨细>)
1. fflush清洗文件缓冲区
2. setbuff 按巨细方位缓冲类型(_IOFBF,_IOLBF,_IONBF)操控缓冲流
文件重命名 rename(旧名,新名)
文件删去 remove(文件名)
文件定位 1.fseek移动到文件的某些方位
2.ftell回来当时文件方位
3. rewind把文件方位设置在开端处
4.fgetpos获取文件方位
5.fsetpos设置文件方位
形式 二进制文件 文本文件
rb r
写(文件无需存在) wb w
追加(文件无需存在) ab a
读和写(从文件头开端) r+b 或 rb+ r+
读和写(假如文件存在就截去) w+b 或 wb+ w+
读和写(假如文件存在就追加) a+b 或 ab+ a+

3. 输入输出

输出类型 函数 表述
输出
printf()
向规范输出stdout写入内容
输出
fprintf(File*,const char *,...)
向恣意输出流写入内容
字符串输出
sprintf(char*,const char *,...)
输出写入字符数组
字符串输出
snprintf(char*,size_t,const char *,...)
输出写入字符数组,约束长度
字符输出
putchar(int)
向规范流stdout写一个字符
字符输出
fputc(int,File*)
恣意流写一个字符
字符输出
putc(int,File*)
恣意流写一个字符,效果同上
输出
puts(const char *)
向规范流stdout写字符串
输出
fputs(const char *,File *)
向恣意流写字符串
输出
fwrite(void*,size_t,size_t,File*)
将内存中的数组仿制给流,操控巨细
输入类型 函数 表述
输入
scanf()
从规范输入stdin读入内容

fscanf(File*,const char *,...)
从恣意输入流读入内容
字符串
sscanf(char*,const char *,...)
输入写入字符数组,通常用fgets后,再运用sscanf进一步处理
字符
getchar(void)
从规范流stdin读一个字符,#define getchar) getc(stdin)
字符
fgetc(File*)
从恣意流读一个字符
字符
getc(File*)
从恣意流读一个字符,效果同上
字符
ungetc(int,File*)
从恣意流读入的字符“放回”并铲除流的文件结束指示器

gets(char *)
从规范流stdin 读一行

fgets(char *,int n,File *)
从恣意流读字符串,到n-1个,或换行符结束操作

fread(void*,size_t,size_t,File*)
从流读入到数组的元素,操控巨细

代码操练片段链接

二十三、 库对数值和字符数据的支撑

包括浮点,整数巨细,数学核算函数,字符处理,和字符串的处理,此处省掉

二十四、 过错处理

标题
<assert.h> 确诊过错
<errno.h> 1.errno变量表明过错
2.<stdio.h>中的perron(const char *)函数显现犯错信息
3.<string.h>中的strerror(int)指向描绘这个过错的字符串
<signal.h> 在UNIX的信号评论更丰厚,运用场景更丰厚
1. signal函数
2.raise函数
<setjmp.h> 函数间跳转
1. int setjmp(jmp_buf env)符号一个方位
2.void longjmp(jum_buf env,int val)回来符号的方位

二十五、 国际化

包括<local.h>、多字节字符和宽字符、双字符三字符、通用字符名,<wchar.h>扩展多字节、<wctype.h>宽字符分类和映射东西

二十六、 其他库函数

标题
<stdarg.h> 可变参数 va_arg,va_start,va_end,va_copy宏
<stdlib.h> 通用运用东西

总结

  1. 《C Programing:A Modern Approch》2th 是一本优秀的C言语学习书本,由浅入深介绍,对读者非常友爱,学后还能依据重复部分再进一步整理缩减。
  2. 关于图形编程,多线程,进程知识点是后边需求参照其他书本进行弥补
  3. 实践代码加深形象