C语言基础知识

非常基础的知识,适合初学者,这是自己开始学习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语言中文网)

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语言中文网)

  • 我们可以使用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”进行编译。

宏定义#

  • 用来将宏参数转换为字符串,也就是在宏参数的开头和末尾添加引号;
  • 示例:
     #include <stdio.h>
    #define STR(s) #s
    int main() {
        printf("%s\n", STR(c.biancheng.net));
        //展开就是:printf("%s", "c.biancheng.net");
        printf("%s\n", STR("c.biancheng.net"));
        //展开就是:printf("%s", "\"c.biancheng.net\"");
        //可以发现,即使给宏参数“传递”的数据中包含引号,
        //使用#仍然会在两头添加新的引号,而原来的引号会被转义。
        return 0;
    }
    //运行结果:
     c.biancheng.net
    "c.biancheng.net"

宏定义##

  • 用来将其他变量连接起来
    #include <stdio.h>
    #define CON1(a, b) a##e##b
    #define CON2(a, b) a##b##00
    int main() {
        printf("%f\n", CON1(8.5, 2));
        //展开为:printf("%f\n", 8.5e2);
        printf("%d\n", CON2(12, 34));
        //展开为:printf("%d\n", 123400);
        return 0;
    }
//运行结果:
850.000000
123400

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语言中文网)

  • 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);

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

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 数字20 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读