欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C语言基础知识

程序员文章站 2024-03-15 12:41:23
...

非常基础的知识,适合初学者,这是自己开始学习C语言的一个小记录,参考C语言中文网来学习的。

1)输出

格式控制符 说明
%c 输出一个单一的字符
%hd、%d、%ld 以十进制、有符号的形式输出 short、int、long 类型的整数
%hu、%u、%lu 以十进制、无符号的形式输出 short、int、long 类型的整数
%ho、%o、%lo 以八进制、不带前缀、无符号的形式输出 short、int、long 类型的整数
%#ho、%#o、%#lo 以八进制、带前缀、无符号的形式输出 short、int、long 类型的整数
%hx、%x、%lx %hX、%X、%lX 以十六进制、不带前缀、无符号的形式输出 short、int、long 类型的整数。如果 x 小写,那么输出的十六进制数字也小写;如果 X 大写,那么输出的十六进制数字也大写。
%#hx、%#x、%#lx %#hX、%#X、%#lX 以十六进制、带前缀、无符号的形式输出 short、int、long 类型的整数。如果 x 小写,那么输出的十六进制数字和前缀都小写;如果 X 大写,那么输出的十六进制数字和前缀都大写。
%f、%lf 以十进制的形式输出 float、double 类型的小数,小数点后面为6位数,不足补0
%e、%le %E、%lE 以指数的形式输出 float、double 类型的小数。如果 e 小写,那么输出结果中的 e 也小写;如果 E 大写,那么输出结果中的 E 也大写。
%g、%lg %G、%lG 以十进制和指数中较短的形式输出 float、double 类型的小数,并且小数部分的最后不会添加多余的 0。如果 g 小写,那么当以指数形式输出时 e 也小写;如果 G 大写,那么当以指数形式输出时 E 也大写。 g规定整个数为6位(整数小数一起)
%s 输出一个字符串

例子1:

int a1=20, a2=345, a3=700, a4=22;
printf("%-9d %-9d %-9d %-9d\n", a1, a2, a3, a4);
输出:20        345       700       22
##%-9d中,d表示以十进制输出,9表示最少占9个字符的宽度,宽度不足以空格补齐,-表示左对齐。综合起来,    %-9d表示以十进制输出,左对齐,宽度最小为9个字符。(20       ---->20一起,占9个字符)
  • 小数默认为double类型
  • puts():输出字符串并自动换行,该函数只能输出字符串,与gets()输入对应

2)输入

函数 缓冲区 头文件 适用平台
scanf()(适用各种数据) 有缓冲区 stdio.h 所有
gets()(用来输入字符串的) 有缓冲区 stdio.h 所有
getchar()用来输入单个字符的) 有缓冲区 stdio.h 所有
getch()(不回显) 无缓冲区 conio.h 只适用Windows
getche() 无缓冲区 conio.h 只适用Windows

注:1、gets() 能读取含有空格的字符串,而 scanf() 不能;

scanf() 读取字符串时以空格为分隔,遇到空格就认为当前字符串结束了,所以无法读取含有空格的字符串。

 而gets() 认为空格也是字符串的一部分,只有遇到回车键时才认为字符串输入结束,所以,不管输入了多少个空格,只要不按下回车键,对 gets() 来说就是一个完整的字符串。

2、getch/getche无缓冲区,输入一个字符后会立即读取,不用等待用户按下回车键;

3、scanf() 可以一次性读取多份类型相同或者不同的数据,getchar()、getche()、getch() 和 gets() 每次只能读取一份特定类型的数据,不能一次性读取多份数据。

1、scanf

  • 在读取字符串的时候,scanf() 遇到空格就认为字符串结束了,不再继续读取了,所以无法读取含有空格的字符串;

  • scanf() 并不是直接让用户从键盘输入数据,而是先检查缓冲区,处理缓冲区中的数据,如果缓存中有数据,而你又不重新输入,那么就会直接从缓存中读取数据;

  • 会忽略换行;

  • 空白符在大部分情况下都可以忽略,但是当控制字符串不是以格式控制符 %d、%c、%f 等开头时,空白符就不能忽略了,它会参与匹配过程,如果匹配失败,就意味着 scanf() 读取失败了。scanf(“a=%d”, &a);

  • 扩展:scanf 加上:{%[*] [width] [size]type | ’ ’ | ‘\t’ | ‘\n’ | };

    scanf("%[abcd]", ptr);           ##只读取abcd这几个字符,一旦遇到不是这几个字符的,就不读取了
    scanf("%[^abcd]", ptr);        ##只要读取到abcd任意一个字符,就停止读取
    scanf("%10[^abcd]", ptr);   ##这样结果字符串最多只能包含10个字符(除'/0'字符外)。
    scanf("%*s", ptr);                   ##只读取,不赋值,ptr的值不会因为这句话而改变
    

2、getchar

  • 如果缓冲区中没有内容,那么等待用户输入;如果有内容,哪怕一个字符(回车、换行也算),也会直接从缓冲区中读取数据,不会等待用户输入。
  • 例子:如果不适用getchar函数,那么输入’a=100’后,按回车,就会直接输出,不会等b的输入;使用了之后,按回车,继续输入 b=100;
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a=0, b=0;
scanf("a=%d", &a);
getchar();   //借助该函数将 \n 从缓冲区中清除:
scanf("b=%d", &b);
printf("a=%d, b=%d\n", a, b);
system("pause");
return 0;
}

运行结果:
a=100↙
b=100↙
a=100, b=100

3)类型转换

自动转换规则:

  • 左边的可以 自动向右边转换,注意float是向double转换的
    C语言基础知识(图片来源:C语言中文网)

4)字符串的定义

  • char str1[]=“helloC!!”; 读取、写入(一般用这个,既满足输出又满足输入)
  • char *str2=“helloC!!”; 读取

printf()、puts() 等字符串输出函数只要求字符串有读取权限,而 scanf()、gets() 等字符串输入函数要求字符串有写入权限,所以,第一种形式的字符串既可以用于输出函数又可以用于输入函数,而第二种形式的字符串只能用于输出函数。

  • 字符数组只有在定义时才能将整个字符串一次性地赋值给它,一旦定义完了,就只能一个字符一个字符地赋值了

    1char str[7];
       str = "abc123";  //错误
    
    2、str[0] = 'a'; str[1] = 'b'; str[2] = 'c';//正确
    
    3char str[]="abc123";    //正确
    
  • 字符串结束标志

在C语言中,字符串总是以`'\0'`作为结尾,
所以`'\0'`也被称为字符串结束标志,或者字符串结束符。

char str[]="123asd" //会自动添加'\0'  实际上数组长度为7

char str[] = {'a', 'b', 'c'};
需要注意的是,逐个字符地给数组赋值并不会自动添加'\0',例如:
数组 str 的长度为 3,而不是 4,因为最后没有'\0',
这就需要我们手动去添加了:char str[] = {'a', 'b', 'c','\0'}


char str[7] = "abc123";
"abc123"看起来只包含了 6 个字符,我们却将 str 的长度定义为 7,
就是为了能够容纳最后的'\0'。如果将 str 的长度定义为 6,它就无法容纳'\0'了。

str[i] = 0;  //也可以写作 str[i] = '\0';根据 ASCII 码表,字符'\0'的编码值就是 0。

更加专业的做法是将数组的所有元素都初始化为“零”值
char str[30] = {0};  //将所有元素都初始化为 0,或者说 '\0'
``````c
在C语言中,字符串总是以`'\0'`作为结尾,
所以`'\0'`也被称为字符串结束标志,或者字符串结束符。

char str[]="123asd" //会自动添加'\0'  实际上数组长度为7

char str[] = {'a', 'b', 'c'};
需要注意的是,逐个字符地给数组赋值并不会自动添加'\0',例如:
数组 str 的长度为 3,而不是 4,因为最后没有'\0',
这就需要我们手动去添加了:char str[] = {'a', 'b', 'c','\0'}


char str[7] = "abc123";
"abc123"看起来只包含了 6 个字符,我们却将 str 的长度定义为 7,
就是为了能够容纳最后的'\0'。如果将 str 的长度定义为 6,它就无法容纳'\0'了。

str[i] = 0;  //也可以写作 str[i] = '\0';根据 ASCII 码表,字符'\0'的编码值就是 0。

更加专业的做法是将数组的所有元素都初始化为“零”值
char str[30] = {0};  //将所有元素都初始化为 0,或者说 '\0'
  • 求字符串长度 strlen(字符串名字strname),返回的是一个整数,不包括’\0’

    在头文件<string.h>里面

5)字符串常用处理函数(string.h)????

函数名 作用 备注
strcat(str1,str2) 把str1和str2拼接起来 返回值为 str1 的地址,str1的长度要>=(str1的长度+str2的长度)
strcpoy(str1,str2) 把str2的内容复制到str1中 str1的内容会被覆盖,str1的长度要>=str2的长度
strcmp(str1,str2) 若 str1 和 str2 相同,则返回0;若 str1 大于str2,则返回大于 0 的值;若str1 小于 str2,则返回小于0 的值 字符本身没有大小之分,strcmp() 以各个字符对应的 ASCII 码值进行比较。 从两个字符串的第 0 个字符开始比较,如果它们相等,就继续比较下一个字符,直到遇见不同的字符,或者到字符串的末尾。
1.strstr(str1,str2) 2. strchr(str1,‘单个字符’),返回该字符在str1中第一次出现的位置,从1开始 查看str1中是否有子串str2;若有,返回从该子串后面所有的字符,若无,返回’ '空 char str[] =“This is a simple string”;
char * pch;
pch = strstr (str,“simple”);
puts (str)// 输出simple string

6)运算符优先级

优先级 ????

算术运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符;

括号>单目运算符>双目运算符>三目运算符;

注:逻辑运算符中“逻辑非 !”除外(需了解!!)

7)选择结构与循环结构

  • switch case:
1case 后面必须是一个整数,或者是结果为整数的表达式,但不能包含任何变量

2default 不是必须的。当没有 default 时,如果所有 case 都匹配失败,那么就什么都不执行
  • while:

    //统计从键盘输入的一行字符的个数,
    //本例程序中的循环条件为getchar()!='\n'
    //其意义是,只要从键盘输入的字符不是回车就继续循环。
    //循环体n++;完成对输入字符个数计数。
    #include <stdio.h>
    int main(){
        int n=0;
        printf("Input a string:");
        while(getchar()!='\n') n++;
        printf("Number of characters: %d\n", n);
        return 0;
    }
    
  • break:

一个break语句只向外跳一层

8)内存管理????

C语言基础知识(图片来源:C语言中文网)

  • 我们可以使用size命令来查看一个目标文件各个段的大小 ????
命令:size a.out
显示:
text	   data	    bss	    dec	        hex	         filename
1896	  616	     8	           2520	        9d8	    a.out

注:dec:十进制总和
         hex:十六制总和
int a = 0;     /* a在全局已初始化数据区 */
char *p1;      /* p1在BSS区(未初始化全局变量) */

int main(void) 
{
  int b;        /* b在栈区 */

  char s[] = "abc";   /* s为数组变量, 存储在栈区 */
      /* "abc"为字符串常量, 存储在已初始化数据区 */

  char *p1, p2;   /* p1、p2在栈区 */
  char *p3 = "123456";  /* "123456\0"已初始化在数据区, p3在栈区 */static int c = 0;  /* c为全局(静态)数据, 存在于已初始化数据区 */
                    /* 另外, 静态数据会自动初始化 */

  p1 = (char *)malloc(10);   /* 分配的10个字节的区域存在于堆区 */   或者p1 = (char *)malloc(10*sizeof(int));
  p2 = (char *)malloc(20);   /* 分配得来的20个字节的区域存在于堆区 */
  free(p1);
  free(p2);
}

1、BSS段

  • 通常用来存放程序中未初始化的全局变量和静态变量(变量刚刚被定义时,系统默认为0或者null,还没有被程序员赋值)
int sum[1000];//默认数组里面每个数为0

2、数据区(data)

  • 通常用来存放*被明确初始化的全局变量、静态变量(包括全局静态和局部静态)、常量数据(常量字符串char str=“hello!!”)
//一个不在任何函数内的声明(全局数据):
 int maxcount = 99;
//使得变量maxcount根据其初始值被存储到初始化数据区中。

static mincount = 100; 
//这声明了一个静态数据,如果是在任何函数体外声明,则表示其为一个全局静态变量;
//如果在函数体内(局部),则表示其为一个局部静态变量;
//另外,如果在函数名前加上static,则表示此函数只能在当前文件中被调用。

3、代码区(text)

  • 通常是指用来存放程序执行代码的一块内存区域,代码区通常是只读的,防止代码被修改;

  • 在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等;

4、栈区(stack)

  • 栈又称堆栈, 是用户存放函数的参数值、程序临时创建的局部变量,也就是说我们函数括弧"{}"中定义的变量(但不包括static 声明的变量,static 意味着在数据段中存放变量);
  • 我们可以把堆栈看成一个寄存、交换临时数据的内存区;
  • 是一块连续的内存区域;

5、堆区(heap)

  • 存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc 等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减),一般由程序员分配和释放,若程序员不释放,程序结束时有可能由OS回收;

  • 堆段由程序员自己管理,即显式地申请和释放空间;

  • 静态分配:编译器在处理程序源代码时分配。

    动态分配:程序在执行时调用malloc库函数申请分配。

  • 函数:

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

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

9)函数声明和函数定义

  • 对于多个文件的程序,通常是将函数 [变量也可以] 定义放到源文件(.c文件)中,将函数的声明放到头文件(.h文件)中,使用函数时引入对应的头文件就可以,编译器会在链接阶段找到函数体。
  • 有了函数声明,函数定义就可以出现在任何地方了,甚至是其他文件、静态链接库、动态链接库等。
#include <stdio.h>
//函数声明
int sum(int m, int n);  //也可以写作int sum(int, int);
int main(){
    int begin = 5, end = 86;
    int result = sum(begin, end);
    printf("The sum from %d to %d is %d\n", begin, end, result);
    return 0;
}
//函数定义
int sum(int m, int n){
    int i, sum=0;
    for(i=m; i<=n; i++){
        sum+=i;
    }
    return sum;
}

10)作用域

  • 局部变量:定义在函数内部的变量,它的作用域仅限于函数内部, 离开该函数后就是无效的,再使用就会报错。

  • 全局变量:在所有函数外部定义的变量,它的作用域默认是整个程序,也就是所有的源文件,包括 .c 和 .h 文件。

  • 变量的使用遵循就近原则,如果在当前的局部作用域中找到了同名变量,就不会再去更大的全局作用域中查找.

  • 只能从小的作用域向大的作用域中去寻找变量,而不能反过来,使用更小的作用域中的变量。

int a, b;  //全局变量,对所有函数都有效
void func1(){
    //TODO:
}
float x,y;  //全局变量,对fun1()无效
int func2(){
    //TODO:
}
int main(){
    //TODO:
    return 0;
}
/*a、b、x、y 都是在函数外部定义的全局变量。C语言代码是从前往后依次执行的,由于 x、y 定义在函数 func1() 之后,所以在 func1() 内无效;而 a、b 定义在源程序的开头,所以在 func1()、func2() 和 main() 内都有效。*/

11)预处理命令

常用预处理

指令 说明
#include 包含一个源代码文件
#define 定义宏
#undef 取消已定义的宏
#if 如果给定条件为真,则编译下面代码
#ifdef 如果宏已经定义,则编译下面代码
#ifndef 如果宏没有定义,则编译下面代码
#elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个#if……#else条件编译块

#include

  • 概念:叫做文件包含命令,用来引入对应的头文件(.h文件)
  • 注:使用尖括号< >和双引号" "的区别在于头文件的搜索路径不同:
1、使用尖括号`< >`,编译器会到系统路径下查找头文件;
2、而使用双引号`" "`,编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。
##也就是说,使用双引号比使用尖括号多了一个查找路径,它的功能更为强大。

#define 宏定义

  • 概念:所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串,是简单的字符串替换。

  • 格式:#define 宏名 字符串字符串可以是数字、表达式、if 语句、函数等】

  • 这里所说的字符串是一般意义上的字符序列,不要和C语言中的字符串等同,它不需要双引号程序中反复使用的表达式就可以使用宏定义,例如:

    #define M (n*n+3*n)
    #undef  M
    //其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用
    
    #define M(y) y*y+3*y  //带参数的宏定义
    // TODO:
    k=M(5);  //宏调用
    
    #define N 100
    void main(){}
    int a=20+N;}  //a为120
    表示之后代码中的N其实代表100
    
  • #if、#ifdef、#ifndef

    • #if 后面跟的是“整型常量表达式”,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名
    • #ifdef 可以认为是 #if defined 的缩写,它的意思是,如果当前的宏已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译。

12)枚举enum关键字

  • 枚举和宏其实非常类似:宏在预处理阶段将名字替换成对应的值,枚举在编译阶段将名字替换成对应的值。我们可以将枚举理解为编译阶段的宏;
  • 其实就是优化了#define命令,因为#define一次只能定义一个宏名,如果有很多都需要去定义,那么会导致宏名过多,代码松散,不够简洁。C此时便可以使用枚举(Enum)类型,一次性能够列出所有可能的取值,并给它们取一个名字。
  • 作用范围是全局的
  • 格式:
enum typeName{ valueName1, valueName2, valueName3, ...... };

如:
enum week{ Mon, Tues, Wed, Thurs, Fri, Sat, Sun };
//枚举值默认从 0 开始,往后逐个加 1(递增);也就是说,week 中的 Mon、Tues ...... Sun 对应的值分别为 0、1 ...... 6。

//我们也可以给每个名字都指定一个值:
enum week{ Mon = 1, Tues = 2, Wed = 3, Thurs = 4, Fri = 5, Sat = 6, Sun = 7 };

//更为简单的方法是只给第一个名字指定值:
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };

//同时定义多个
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
enum week a = Mon, b = Wed, c = Sat;

13)指针????

  • 概念:将内存中字节的编号称为 地址(Address)或 指针(Pointer),即数据在内存中的地址称为指针,如果一个变量存储了一份数据的指针,我们就称它为指针变量
  • 定义的时候需要带 ’ * ',之后赋值的时候可以不带
  • 深入了解,参考该文????
##变量指针
int a=100,b=200;
int *p;   //或者定义并赋值:int *p=&a;
p=&a;   //此处的p不用加*,代表指向a,获得a的地址;       int *p=&a
p=&b;  //指针的值可以改变                                                             *p=100   此处是改变p的值。而不是地址,需要加 *          

##连续定义:
int *a, *b, *c;  //a、b、c 的类型都是 int*
  • a 只需要一次运算就能够取得数据,而 *p 要经过两次运算,多了一层“间接”;使用指针是间接获取数据,使用变量名是直接获取数据ps=stus,前者比后者的代价要高

C语言基础知识(图片来源:C语言中文网)

  • a 和*p是等价的, &a 和 p是等价的; *p值改变的话,a的值也会变
#include<stdio.h>
int main()
 {
    int a=12;
    int*pa=&a;
    printf("a=%d,   *pa=%d\n",a,*pa);         //a和*p的值都是12
    printf("&a=%#X,   pa=%#X\n", &a,pa);     //&a 和 p的都是0X859ACE3C


    int b=100;
    int *pb=&b;
    printf("b=%d,   *pb=%d\n", b, *pb);
    printf("&b=%#X,   pb=%#X\n", &b, pb) ;


    *pa=*pb;//改变*pa的值,也就改变了a的值
    printf("pa=%#X,   pb=%#X\n", pa, pb);//但是pa的指向并没有变,仍然指向a
    printf("a=%d,   *pa=%d\n", a, *pa);//a的值变为100


    //  pa=pb;//改变了pa的指向,使它指向了b
    //  printf("pa=%#X,   pb=%#X\nps=stusps=stus", pa, pb); //pa的指向和pb的一样
    //  printf("a=%d,   *pa=%d\n", a, *pa);//但是a的值没有变,只是pa不再指向a了
    return 0;
}
  • 使用 指针来遍历数组
#include <stdio.h>
int main()
{
    int arr[] = { 99, 15, 100, 888, 252 };
    int len = sizeof(arr) / sizeof(int);  //求数组长度
    int i;
    for(i=0; i<len; i++)
    {
        printf("%d  ", *(arr+i) ); //*(arr+i)等价于arr[i]
    }     //使*p=arr后,也可以用*(p+1)来遍历数组
    printf("\n");
    return 0;
}


#数组指针,数组指针指向的是数组中的一个具体元素,而不是整个数组
int *p=arr;//arr 本身就是一个指针,可以直接赋值给指针变量 p
##或者
int *p=&arr[0];//arr 是数组第 0 个元素的地址,因此arr、p、&arr[0] 这三种写法都是等价的,它们都指向数组第 0 个元素,或者说指向数组的开头

int *p=&arr[2];  也可以写成  int *p=arr+2;//因为arr和arr[0]等价!!
  • 二维数组指针????: *(*(p+0)+0) 表示二维数组的第一行第一个数(????有点复杂,需仔细理解 )
int (*p)[4] = a;//括号中的*表明 p 是一个指针,它指向一个数组,数组的类型为int [4],这正是 a 所包含的每个一维数组的类型。

//等价关系:
a+i == p+i
a[i] == p[i] == *(a+i) == *(p+i)
a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j) == *(*(p+i)+j)

int *(p1[5]);  //指针数组,是一个数组,只是每个元素保存的都是指针,可以去掉括号直
int *p1[5];
int (*p2)[5];  //二维数组指针,是一个指针,它指向一个二维数组,不能去掉括号

14)结构体(struct)

  • 用来存放一组不同类型的数据,是一种数据类型,是一种创建变量的模板,编译器不会为它分配内存空间;
  • 格式
    一般格式:
struct 结构体名{ps=stus
    结构体所包含的变量或数组
};

struct stu
{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在学习小组
    float score;  //成绩
} stu1, stu2;//创建结构体 名为 stu,同时定义 两个结构体变量

struct stu stu1, stu2;//定义结构体变量
stu1.name="tom";//获取结构体里面的变量

struct stu *pstu = &stu1;//结构体指针,与数组不一样,要想取得结构体变量的地址,必须在前面加&
pstu->name="tom";//结构体指针获取结构体里面的变量

稍微复杂一点,需要仔细理解:

struct stu
{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组
    float score;  //成绩
}stus[] = {//定义了名为stus的结构体数组,并为其赋值
    {"Zhou ping", 5, 18, 'C', 145.0},
    {"Wang ming", 3, 17, 'B', 144.5}
}, *ps;//定义了一个结构体指针

位域

  • 是一类特殊的结构体,当你只想使用数字0或1的时候,就可以使用这个结构体,设置位域为1;
  • 格式:
    struct
    {
    type [member_name] : width ;
    };
  • 描述:
    • type 只能为 int(整型),unsigned int(无符号整型),signed int(有符号整型) 三种类型,决定了如何解释位域的值。
    • member_name 位域的名称。
    • width 位域中位的数量。宽度必须小于或等于指定类型的位宽度。
  • 示例:
    /*
    通常当你定义一个int类型的变量时,
    你只要用到8以内的数,那么此时你就可以用位域来进行设置了
    8的二进制数是1010,四位数,那么我们就设置位域为3,
    就是只可以用0~7这8个数,如果一旦大于7 ,就会报warnning,将变量置为0
    */
    #include<stdio.h>
    int main(int argc, char const *argv[])
    {
        struct  number
        {
            unsigned int age:3;//表示该变量占4个字节,32位,但是只有3位可以被使用
        };
        struct number a;
        a.age=4;
        printf("%d\n",sizeof(a));//4
        a.age=3;
        printf("%d\n",a.age);//3
    
        a.age=8;
        printf("%d\n",a.age);//0
        
        return 0;
    }
    

15)typedef关键字

  • 使用关键字 typedef 可以为类型起一个新的别名。typedef 的用法一般为:
typedef  oldName  newName;

//如给结构体起别名
typedef struct stu
{
    char name[20];
    int age;
    char sex;
} STU;
STU body1,body2;

//为指针类型定义别名:表示 PTR_TO_ARR 是类型int * [4]的别名,它是一个二维数组指针类型
typedef int (*PTR_TO_ARR)[4];
PTR_TO_ARR p1, p2;
*(p1+1)//表示二维数组的第一行
  • typedef与#define的区别 :
//1) #define可以使用其他类型说明符对宏类型名进行扩展,但对 typedef 所定义的类型名却不能这样做
#define INTERGE int
unsigned INTERGE n;  //没问题

typedef int INTERGE;
unsigned INTERGE n;  //错误,不能在 INTERGE 前面添加 unsigned

//2)在连续定义几个变量的时候,typedef 能够保证定义的所有变量均为同一类型,而 #define 则无法保证。例如:
#define PTR_INT int *
PTR_INT p1, p2;
经过宏替换以后,第二行变为:
int *p1, p2;
这使得 p1、p2 成为不同的类型:p1 是指向 int 类型的指针,p2 是 int 类型。

相反,在下面的代码中:
typedef int * PTR_INT
PTR_INT p1, p2;
p1、p2 类型相同,它们都是指向 int 类型的指针。

16)const关键字

  • 使得变量的值或者是指针的指向不能被修改,被const修饰以后,变量就相当于一个常量了;
  • 语法及使用:
const int MAXAGE=101;//MAXAGE被固定,之后不能再修改它的值
MAXAGE=200//会报错
  • const和指针一起使用
//可以修改指向,但是指向的数据不能被修改,
//如p1指向a,a的值不可以修改,但是p1可以改变指向,可以指向b;
const int *p1;    
int const *p2;  

 //只读,p3本身的值不能被修改,指针的指向不能变,但是指向的数值可以被修改
//如果p3指向的是a,那么就不可以再指向别的数据,但是a的值可以修改
int * const p3;  

//指向和指向的数据都不能修改
const int * const p4;
int const * const p5;

const:读取

非const:读取、写入

总结:将非 const 类型转换为 const 类型是允许的,降低权限。

17)static关键字

  • 使用static修饰局部变量

    • 静态局部变量存储于进程的全局数据区,即使函数返回,它的值也会保持不变
  • 使用static修饰全局变量

    • 普通全局变量对整个工程可见,其他文件可以使用extern外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。
    • 静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。
  • 使用static修饰函数

    • 静态函数只能在声明它的文件中可见,其他文件不能引用该函数
    • 不同的文件可以使用相同名字的静态函数,互不影响

18)extern关键字

  • 可以使用在后面定义的全局变量和函数
#include<stdio.h>
int main()
{
    func(); //1
    extern int num;//对后面的全局变量声明
    extern int func();//对后面的函数声明
    printf("%d",num); //2
    return 0;
}

int num = 3;

int func()
{
    printf("%d\n",num);
}
  • 在一个文件中引用另外一个文件中的全局变量和函数(局部变量不可以)
//main.c
#include<stdio.h>
int main()
{
    extern int num;//使用a.c中的全局变量num
    printf("%d",num);
    return 0;
}


//a.c
#include<stdio.h>
int num = 5;
void func()
{
    printf("fun in a.c");
}

注:extern关键字只需要指明类型和变量名就行了,不能再重新赋值;

但是在声明之后就可以使用变量名进行修改了,如下:

    extern int num;
    num=1;

19)命令行参数

  • 执行程序时,可以从命令行传值给 C 程序,从外部控制程序,而不是在代码内对这些值进行硬编码
  • 使用 main() 函数参数来处理:main(int argc , char *argv[])/main(int argc , char **argv)
    • argc 是指传入参数的个数;
    • argv[] 是一个指针数组,指向传递给程序的每个参数/**argv是一个二级指针,指针字符串;
    • argv[0] 存储程序的名称(a.out),argv[1] 是一个指向第一个命令行参数的指针,*argv[n] 是最后一个参数。如果没有提供任何参数,argc 将为 1,否则,如果传递了一个参数,argc 将被设置为 2。
    • 使用方法:在命令行输入:/a.out 命令行参数1 命令行参数2,…

20)文件读写

访问模式

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

如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:

"rb", "wb", "ab", "rb+", "r+b", 
"wb+", "w+b", "ab+", "a+b"

打开文件

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

写入文件

  • int fputc( int c, FILE *fp ); 只读取一个字符
  • char *fgets( char *buf, int n, FILE *fp ); 遇到换行就不读了
  • int fscanf(FILE *fp, const char *format, …); 遇到空格或换行符号就不会读了

关闭文件

  • int fclose( FILE *fp );

二进制 I/O 函数

下面两个函数用于二进制输入和输出:

  • size_t fread(void *ptr, size_t size_of_elements,
    size_t number_of_elements, FILE *a_file);

  • size_t fwrite(const void *ptr, size_t size_of_elements,
    size_t number_of_elements, FILE *a_file);

这两个函数都是用于存储块的读写 - 通常是数组或结构体。