likes
comments
collection
share

语言篇|现代C记录—简略篇

作者站长头像
站长
· 阅读数 4

一.前言

导图 语言篇|现代C记录—简略篇

学而时习之,不亦说乎

二.基本篇

2.1 HelloWorld

基本结构: 指令、函数、语句、显示、注释

#include <stdio.h>  
  
int main(int argc, char **argv) {  
  printf("Hello, World!\n");  
  return 0;
}  
  

编译输出:gcc/cc -o <outputfile> <sourcefile> [-w]

$ gcc hello.c  
  
$ ./a.out  
Hello, World!

2.2 类型

语言篇|现代C记录—简略篇

2.2.1 基本类型

数值&字符

基本类型表述一般读写格式
short、int、long、long long有符号整数,最左边位为0代表正数,1代表负数%<h\l><d\o\u\x>
在上面前加unsigned无符号整数
float单精度浮点数%f , %g , %e
double双精度浮点数%f , %g , %e 前加小l
long double扩展浮点数%f , %g , %e 前加大L
char字符类型%c

2.2.2 其他类型

数组

#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];
 }

字符串

char *p_str = "hello world!";\\指针式
char arry_str[] = "hello world!"; \\数组式

结构、联合、枚举

#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类型定义

2.3 运算符与表达式

语言篇|现代C记录—简略篇

运算符

优先级名称符号结合性
1数组取下标[]左结合性
1函数调用()左结合性
1取结构和联合的成员. 右箭头->左结合性
1自增(后级)i++左结合性
1自减(后缀)i--左结合性
2自增(前缀)++i右结合性
2自减(前缀)--i右结合性
2取地址&右结合性
2间接寻址*右结合性
2一元正号+右结合性
2一元负号-右结合性
2按位求反~右结合性
2逻辑非!右结合性
2计算所需空间sizeof右结合性
3强制类型转换()右结合性
4乘法类运算符* / %左结合性
5加法类运算符+ -左结合性
6移位<< >>左结合性
7关系< > <= >=左结合性
8判等== !=左结合性
9按位与&左结合性
10按位异或^左结合性
11按位或|左结合性
12逻辑与&&左结合性
13逻辑或||左结合性
14条件? :左结合性
15赋值= *= /= %= += -= <<= >>= &= ^= |=右结核性
16逗号,左结合性

表达式

主要包含逻辑、算术、赋值表达式,前两种参考上方表格,赋值表达式注意一下复合赋值


  int android, java, python,c;
   /*简单赋值*/
  android = 1;
  java = android;
  c = android + java * 10;
  
  /*复合赋值*/
  c = android = python = java * 10;

控制流程

if,switch,while,for,break,continue,goto,参考上一篇。此处额外列举一下longjump

#include <setjmp.h>  
  
void callee(jmp_buf env) {  
  longjmp(env, 1);  
  /* unreachable */  
}  
  
void caller() {  
  jmp_buf env;  
  
  switch (setjmp(env)) {  
  case 0:  
    callee(env);  
    /* unreachable */  
    break;  
  case 1:  
    printf("returned from callee\n");  
    break;  
  default:  
    printf("unexpected setjump value\n");  
  }  
}

三. 进阶篇

3.1 指针

语言篇|现代C记录—简略篇

定义、赋值、取址&、间接寻址*

int main(){
    int i=0,*p;//声明指针变量p
    p=&i;// & 取址运算符,把i的地址赋值给了指针p
    int k=1,*q=&k;//合并写法
    printf("%d",*p);// *(间接寻址运算符)访问存储在对象中的内容 ,也可想象成&的逆运算
}

作为返回值

int *find_middle(int n, int a[n]) {
  return &a[n / 2];
}

作为参数

重要作用

  • 函数调用中用作实际参数的变量无法改变,当希望函数能够改变变量时, 指针作为参数就能解决此问题。
  • 指针传参高效原因是,如果变量需要大量存储空间,传递变量的值有时会浪费时间和空间。
void decompose(double x, long *int_part, double *frac_part) {
  *int_part = (long) x;
  *frac_part = x - *int_part;
}

算术运算

int main() {
  int a[] = {1, 2, 3, 5, 6, 7, 8, 9, 10};
  int *p = &a[0], *q = &a[6];
  //1. 加减整数
  p += 4;
  p -= 3;
  
  //2. 指针相减(相减为距离)
  printf("q-p = %lld\n", q - p);

  //3. 指针比较(取决于在数组中的位置)
  printf("q>p:%d q<p:%d\n", q > p, q < p);

  //4. 复合常量的指针
  int *k = (int[]) {2, 4, 6, 8, 10};
  k += 4;
  printf("*K=%d\n", *k);

  return 0;

}

数组名作为指针指针作为数组名

#define N 10
/* 1 .用数组名作为指针*/
void case1() {
  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++) {
      //省略
  }
  // 惯用法(更简洁)
  for (p = a; p < a + N; p++) {//此处a+N理解为,将a当作指针,然后做了指针的加法运算
  }
}

/* 2. 用指针作为数组名*/
void case1() {
  int a[] = {1, 2, 3, 5}, *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]);
  }
}

数组指针指针数组

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

语言篇|现代C记录—简略篇

指针高级应用malloc、calloc、realloc

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

三种内存分配函数(内存块都来至于堆区)描述原型
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.如果一个对象有多种访问方式,通常将这些方式互称为别名

3.2 预处理器

3.2.1 预处理器原理

语言篇|现代C记录—简略篇

gcc <SourceFile> -E 可以看到预处理器的输出

注意: 预处理器仅知道少量C语言的规则。因此在执行指令时是有可能产生非法程序,有时看起来正常但错误找起来难,可以检查一下预处理输出是一种方式

3.2.2 预处理指令

特征:

  1. 指令都以#开始
  2. 指令的符号间可以插入任意数量空格或水平制表符
  3. 指令总在第一个换行符出结束,除非使用 \ 符加入当前行末尾,明确地指明要延续
  4. 指令可以出现在程序的任何地方
  5. 注释可以和指令放在同一行

部分预处理指令:

预处理指令范畴指令
宏定义#define 指令定义一个宏#undef指令删除一个宏
条件编译#if#ifdef#ifndef#elif#else#endif 以测试条件来确定程序是否包含一段文本块
文件包含#include 指定的文件内容被包含到程序中
其他特殊指令1. #error显示出错消息2. #line 改变程序行编号方式 3. #pragam 为编译器执行某些特殊操作特供一种方法

3.2.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;
}

3.4 程序设计

多文件程序的文件

标题
源文件.c结尾的全部文件
头文件惯例.h结尾的文件

#include 包含规则

#include 记号 ,如#include CPUFILE,CPUFILE是一个根据不同系统下不同架构的宏定义

头文件包含规则
#include <文件名> 搜寻系统头文件
#include "文件名" 搜寻当前目录头文件,后系统文件目录

3.5 IO

语言篇|现代C记录—简略篇

3.5.1 流

标题
文件指针File *fp1 C中对流的访问是通过文件指针实现

标准流

文件指针默认含义
stdin标准输入键盘
stdout标准输出屏幕
stderr标准错误屏幕

重定向

重定向输入:强制程序从文件输入而不是从键盘输入方法:前面加上字符<,如demo <in.dat
重定向输出:强制程序从文件输出而不是从屏幕输出方法:前面加上字符>,如demo >out.dat

3.5.2 文件

<stdio.h>支持的两种类型文件

文本文件字节表示字符,可检查或编辑文件
二进制文件字节不一定表示字符,字节组可以表示其他类型数据

打开、关闭、及其他操作

语言篇|现代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设置文件位置
模式二进制文件文本文件
rbr
写(文件无需存在)wbw
追加(文件无需存在)aba
读和写(从文件头开始)r+b 或 rb+r+
读和写(如果文件存在就截去)w+b 或 wb+w+
读和写(如果文件存在就追加)a+b 或 ab+a+

代码练习片段链接

3.5.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*)从流读入到数组的元素,控制大小

四.标准库

主要分为上面几个部分,参考上篇

总结

  • 整体上包括了C语言语法的概要,本身目的是为了少量描述进行知识概括,但对宏和指针部分还是保留一些细节描述
  • 对于Android JNI,Framework知识所需的进程线程部分知识还需单独文章罗列。