抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

🎈C语言基础入门

一、数据的数据类型、数据的输入输出

(一)C语言中的数据类型

C语言中的数据类型

基本类型

整型(int):大小为4字节

浮点型(实型):image.png

双精度double

单精度float:占用4个字节

字符型:单引号,占用一个字节

字符串型:双引号,占用字节数为字符数量 + 1,因为每个字符串最后都有个\0表示结束!

构造类型

指针类型

空类型

基本数据类型,如果只声明不赋值,那么该变量是否会被赋初始值是不一定的,不同的编译器对该行为的操作不同,有些编译器会赋初始值,有些则不会!

(二)格式化输出函数print(f)的使用

  • 函数声明:

    1
    printf(const char *format, ...);
    • 前面是格式控制符,后面是输出的目标。
    • 控制符用双引号引起来,里面开头用%,接着跟类型符及其他控制符。
  • 类型符:

    • %hd%d%ld 以十进制、有符号的形式输出 short、int、long 类型的整数。
    • %hu%u%lu 以十进制、无符号的形式输出 short、int、long 类型的整数。
    • %lf 以普通方式输出double(float弃用,long doube无用)。
    • %e 以科学计数法输出double。
    • %c 输出字符。
    • %s 输出字符串。
    • %x无符号十楼进制数,用小写字母。
    • %X无符号十楼进制数,用大写字母。
    • %o八进制输出。(这里是字母o而不是数字0!)
    • %p输出一个指针。
    • %%输出一个百分号。
  • 宽度符:

    %后面类型符前面,加上数字,可用于控制输出内容的宽度,数字为几就表示输出总宽度为几:

    1
    printf("%10s", "哈哈");

    imagebf360efccecd70a8.png

  • 对齐标志:

    百分号%后加上加号或减号可更改输出内容的默认对齐方式,加号为右对齐(默认),减号为左对齐:

    1
    printf("%-10s", "哈哈");

    image4b3ba631fa02e843.png

  • 精度控制

    在对齐标志后面添加小数点加规定数字(如.3),即可将输出结果四舍五入到规定的位数(是控制小数点后的位数):

    1
    printf("%-10.3f", 3.1415926535);

    imagec030e95496ed0cba.png

  • 其他

    • 换行,在最后加上\n,就有了输出结束后换行的效果:

      1
      printf("%.3lf\n%.5lf\n", 3.1415926535,3.1415926535);

      imagefb90741a49aa0efb.png

    • 输出临时/匿名常量:

      可在类型符部分的双引号里面加入任何字符串空格或其他字符,控制条台会一起输入到屏幕上:

      1
      printf("%.3lf哈哈哈\n换了行,前面留六个空格:%13.5lf\n", 3.1415926535,3.1415926535);

      image75a1b3c0f89c1a43.png

      在实际开发中也常用这种方式输出内容!

    • 如果在不恰当的位置添加了临时字符,则会使程序输出错误:

      1
      printf("%.3啊啊lf哈哈哈\n换了行,前面留六个空格:%13.5lf\n", 3.1415926535,3.1415926535);

      image07376b2bf2ae9fab.png

(三)整型进制转换

  • 在声明变量的时候,在字面值前面加上字母o即表示该数值为八进制字面值,加上0x即表示为十六进制;

  • 输出的时候可使用格式化输出将原来的值按照不同进制的结果输出:

    1
    2
    int b = 10000;
    printf("%o\n%x\n",b);//输出为八进制数值和十六进制数值

    imagebe9ec4388461b818.png

(四)标准输入函数scanf()函数

初试不考scanf(),复试必考scanf()

参考来源:C语言——scanf()函数的具体详解_%c%c中间的空格_哩lililili的博客-CSDN博客

1、两种用法

  • 读取基本类型变量

    1
    2
    3
    int d1;
    float f1;
    scanf("%d %f", &d1,&f1);
  • 读取字符数组

    1
    2
    char cs[] = {};
    scanf("%c",cs[]);

其实很好理解,使用scanf()读取输入需要确定变量值的地址,因此读取基本类型输入的时候需要使用取地址符& + 变量名,而对于字符数组,它的首元素地址就是这个数组的地址,因此并不需要加取地址符号。

2、机制原理

  • scanf()的处理机制/原理:

    scanf()以删除的方式从缓冲区读入数据(来自标准输入设备的数据存储在缓冲区),也就是说,scanf()从缓冲区读入一个数据项,该数据项在缓冲区中就被清除掉了。而如果scanf()需要读取一个数据项,发现缓冲区当前是空的,那么程序就会在scanf()代码处阻塞,等待用户输入,scanf()函数接收到相应的数据项之后,在缓冲区中将这一数据项清除,scanf()函数返回,程序继续执行。

3、对于不同类型输入的处理方式

  • 输入整数%d或浮点数%f

    对于整型数据的输入,也就是说%d类型的输入,scanf默认的分割符是所有的空白字符(空格,回车和制表符都行)。也就是说如果一个scanf函数中出现scanf("%d%d",&a,&b),那么用任何一个空白字符来分隔两个整数a,b的值,变量a,b都可以接收到正确的输入。同样,浮点数也是如此!

    因此,下列输入是完全没问题的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //代码端:
    int a,b;
    scanf("%d",&a,&b);
    printf("%d\n%d\n",a,b);

    //控制台输入端:
    7 3
    //或
    7 3
    //或
    7
    3

    7
    3

    image2f26cde29216b2e7.png

    注: 输入队列中的7和3以及中间的空格,制表符或者换行符都会被送入缓冲区,但是scanf只接受非空白字符,因此无论以上三种格式输入都不会改变输入结果,理由是scanf会跳过所有的空白字符,将其丢在缓冲区内。所以,程序在下一次读取输入的时候会首先读取到的是被丢弃在缓冲区中的空白字符,如果接下来的转换说明是%c,或者%s,将会直接读取该空白字符而不是跳过,空白字符会从打印队列中显示出来。如何解决?看下文:

  • 输入字符数据%c

    scanf在处理对字符数据的输入时,既不会忽略前导空白字符(空格、换行和Tab),默认也没有任何分隔字符。所有的字符,包括空白字符都会被当成输入字符。

    因此如果在读取字符输入的时候一定要注意前文中的空白字符(空格、制表、换行)!

    **解决方法1:在%c之前加一个空格,即可起到跳过当前输入内容中的所有空白直到遇到非空白字符的作用!!!这同样对%d以及其他格式化输入适用!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //代码端:
    int a,b;
    char str01 = '$';
    char str02 = '(';
    scanf("%d%d %c %c",&a,&b,&str01,&str02);
    printf("%d\n%d\n%c\n%c",a,b,str01,str02);

    //控制台:
    D:\JetBrains\CLion_wangDao\KQProjects\001\cmake-build-debug\001.exe
    12
    23
    z
    x
    12
    23
    z
    x
    进程已结束,退出代码为 0

    imageca4b30f081223418.png

    **解决方法2:使用fflush()函数

    fflush(stdin);作用:清空标准输入缓冲区!

    在输入字符之前先用此函数清空缓冲区,那就没有任何空白了,nice!

二、运算符与表达式

(一)运算符分类

C 语言提供了 13 种类型的运算符,如下所示。

  • 算术运算符(+-*/%);
  • 关系运算符(><==>=<=!=);
  • 逻辑运算符(!&&||);
  • 位运算符(<<>>~|^&);
  • 赋值运算符(=及其扩展赋值运算符);
  • 条件运算符(?:);
  • 逗号运算符(,);
  • 指针运算符(*&);(讲指针时详解)
  • 求字节数运算符(sizeof());
  • 强制类型转换运算符((类型名));
  • 分量运算符(.->);(讲结构体时详解)
  • 下标运算符([]);(讲数组时详解)
  • 其它(如函数调用运算符());(讲函数时详解)

(二)算术运算符

+-*/%

1、+-*

没什么好说的

2、/

操作数都是整型那么结果是整型,有一个是浮点型那么执行的就是浮点型除法。

3、%

对两个整型数值取模,左边的除以右边的,返回余数

比如:

1
2
3
4
5
4 % 50 = 4;
20 % 50 = 20;
50 % 50 = 0;
51 % 50 = 1;
99 % 50 = 49;

4、优先级

*/%+-

(三)关系运算符

><==>=<=!=

对两个值比较,真返回1假返回0,C语言中是没有布尔类型的!0表示假1表示真。

优先级低于算术运算符。

C语言中的==不能用于结构体的比较!都无法编译通过!

(四)运算符优先级

C.png

三、选择、循环

if else、for、while、do while、continue、break,与Java大差不大。

  • for()循环

    1
    2
    3
    for(起始条件; 判断语句; 循环指令){
    代码块
    }

    判断语句为真则执行代码块,直至判断语句为假。

  • while()do while

    1
    2
    3
    4
    5
    6
    7
    while(判断语句){
    代码块
    }
    //-----------------------------------
    do{
    代码块
    }while(判断语句);

    判断语句为真则执行代码块,直至判断语句为假。

    do while循环体最少会执行一次,而while最少执行0次。

用到的时候随时补充。

四、一维数组与字符数组

(一)一维数组

1、声明/初始化

  • 在定义数组的时候对数组元素赋初值:

    1
    int ar01[5] = {20,8,11,65,165};

    不能先声明再赋值:

    1
    2
    3
    4
    5
    int ar01[5];
    ar01[] = {1,1,1,1,1,};
    //或
    ar01 = {1,1,1,1,1};
    //这都是错误写法!!!

    也可以不赋初始值,但是默认会是数值几有点玄学:

    1
    int ar01[10];

    image3876e106b0d23d7c.png

  • 如果给每一个元素都赋具体的初始值,那么可以省略方括号内的元素个数:

    1
    int ar01[] = {3,6,9,12};

    但是初试不建议这么写!还是要加上元素个数!!!

  • 可以只给一部分元素赋值,未赋值的元素将被赋值为0

    1
    int ar01[5] = {1,1,1};

    image.png

    若要给所有元素赋初始值为0,则可以写成int ar01[5] = {0};

  • 可以给单独的某个元素赋值:

    1
    2
    int ar01[5] = {0};
    ar01[0] = 33, ar01[2] = 34;

    image1b1baf0a0263ac61.png

*注:命名规则必须如上,C语言不允许把方括号放到类型名后面!

2、一维数组的存储方式

3、数组的访问越界

//C和C++的特性!

//初始一般考不到,复试有可能问到!

什么是数组访问越界:

1
2
3
int ar01[3] = {5,5,5};
int i = 23, j = 24;
ar01[3] = 11, ar01[4] = 12;

imagee76d93300592d71f.png

当访问某数组元素时不小心将索引值i错写为i > arrar.length的值时,再进行的操作就会作用于其他已有变量从而导致bug!!!

4、数组的传递

//初试和复试都可能考到!

当某个数组从函数A作为参数传递给函数B时,B函数接收到的并不是数组本身,而是数组的内存地址——即一个指针而已!

所以数组的长度是传递不过去的!但是更改其某个元素的数值等操作仍可正常操作。

因此,如果有向其他函数传递数组长度的需要,建议再声明一个参数专门用来存储数组长度。

5、字符数组初始化及传递

  • 初始化方式与之前的相同:

    1
    2
    3
    4
    5
    6
    7
    //可以声明的时候直接赋值,也可以声明后挨个元素单独赋值:
    char ch00[5];
    //或
    char ch01[] = {'伍','六','柒'};
    //或
    char ch02[4];
    ch02[0] = '梅', ch02[0] = '花', ch02[0] = '十', ch02[0] = '三';

    但是工作中不这样敲,字符数组一般用来存储字符串:

    1
    char ch03[] = "19建学(1)班";

    C语言规定字符串的结束标志为\0,而系统会对字符串常量自动添加一个\0,因此,用字符数组存储字符串时,字符串的字符长度必需比所在数组的数组长度少一字节!否则,系统再输出完数组内容后没检测到\0,就会接着在内存中把后面的其他数据输出,直到遇到\0。因此,遇到输出字符串乱码时可检查代码和内存是否遗漏了\0

    注:汉字字符所占空间为一个字符两个字节!而英文字符为1字节。

    1
    2
    3
    char ch03[13] = "19建学(1)班";
    printf("%s",ch03);
    //第一行字符总长度为13字节,但是系统还要追加一个\n,因此数组长度为14,方括号内填13则会出问题:

    image1f644d1adfad9347.png

    数组长度改成14则正常:

    1
    2
    char ch03[14] = "19建学(1)班";
    printf("%s",ch03);

    image11d0d1bd465bd1a0.png

  • 字符串的传递——模拟printf()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include <stdio.h>

    void print(char c01[]){
    int i = 0;
    while(c01[i]){
    printf("%c",c01[i]);
    i++;
    }
    printf("循环结束\n");
    }

    void main(){
    char ch01[] = "splendid";
    print(ch01);
    }

    imagea681bfbca95aa7aa.png

  • scanf()更改字符数组的注意:

    使用scanf()对字符数组内容进行字符串的赋值时,用户在键盘输入完成后,系统还会自动追加一个\n ,且不用对字符数组取地址:

    1
    2
    3
    char str01[15] = {"makabaka"};
    scanf("%s",str01);
    printf("%s",str01);

    image57c02b31eeee8ba6.png

  • 但是这种方法不能输入带有空格的文本,此处系统遇到任何空白都会识别为结束!

    怎么解决?看后面。

6、gets函数与puts函数

(1)gets()

从控制台接受用户输入内容,与scanf()不同的是gets()会存储空格和制表符,用printf()输出时不会有空格或制表符之后的内容被忽略的情况!

  • 输入的参数只能是某个字符数组名——它的指针。
  • 按回车键结束输入,系统仍会追加\n。gets 遇到\n后,不会存储\n,而是将其翻译为空字符\0
  • 参数只能是一个,不能是多个!
1
2
3
char cr02[15];
gets(cr02);
printf("%s",cr02);

imagea29b54a5aa85d742.png

(2)puts()
  • 将字符串打印到屏幕上,然后多打印一个换行符。
  • 相当于printf("%s\n",cr02);或Java的System.out.println();
  • 同上,输入的参数只能是某个字符数组名——它的指针。
1
2
3
char cr02[15];
gets(cr02);
puts(cr02);

imagecf9e7d17157764e6.png

7、str系列字符串操作函数

//初试没那么重要,复试更重要一点!

  • 使用时头文件要加#include <string.h>!!!
(1)strlen(str01):统计字符串长度
  • 传入参数为字符串名。
  • 返回一个int类型的数字结果。
  • 忽略字符串结尾系统追加的\n,但不忽略数组中任何地方的空白。
  • 计算的字符长度为实际存在的有效字符长度,跟你定义字符串数组时的方括号里的数字没关系!
1
2
3
4
5
char cr02[20];
gets(cr02);
puts(cr02);
int crLength = strlen(cr02);
printf("字符串长度:%d",crLength);

imagee2fad1fa346ce457.png

(2)strcpy(strA,strB):复制字符串B到字符数组A中,替换掉原本A中的所有内容!
  • A的所有内容都会被删掉!
  • 第一个参数必须是字符串名/指针,而第二个参数指针或匿名临时字符串都可以!但是参数只能是两个!
  • 使用时注意不要超过定义的字符串长度,否则造成访问越界!
1
2
3
4
5
6
7
8
9
10
11
char cr02[20];
char cr03[10];
printf("请输入第一个字符串:");
gets(cr02);
printf("请输入第二个字符串:");
gets(cr03);
strcpy(cr02,cr03);
int crLength = strlen(cr02);
printf("strcopy()函数处理的结果:");
puts(cr02);
printf("总的字符串长度:%d",crLength);

image97005a2413618f5f.png

(3)strcmp(strA,strB):比较两个字符串的内容
  • 原理:从头到尾比较两个字符串中相同位置的字符的ASCⅡ码值,相同则比较下一位的,不同则返回数字。
  • 返回值为int类型的整数,相同则返回0,A>B(相同位置字符的ACSⅡ码值)返回1,A<B返回-1
  • 输入的两个参数可以是字符串的指针也可以是匿名的临时字符串,二者都可以,但是参数只能是两个!
1
2
3
4
5
6
7
8
9
10
11
12
char cr02[20];
char cr03[10];
printf("请输入第一个字符串:");
gets(cr02);
printf("请输入第二个字符串:");
gets(cr03);
strcat(cr02,cr03);
int crLength = strlen(cr02);
printf("strcopy()函数处理的结果:");
puts(cr02);
int biJiaoJieGuo = strcmp(cr02,cr03);
printf("总的字符串长度:%d\n两个字符串比较的结果:%d\n",crLength,biJiaoJieGuo);

image86e617d5c3f7f2c5.png

(4)strcat(strA,strB):将字符串B连接到字符串A的后面
  • 第一个参数必须是字符串名/指针,而第二个参数指针或匿名临时字符串都可以!但是参数只能是两个!
  • 使用时注意不要超过定义的字符串长度,否则造成访问越界!
1
2
3
4
5
6
7
8
9
10
11
char cr02[20];
char cr03[10];
printf("请输入第一个字符串:");
gets(cr02);
printf("请输入第二个字符串:");
gets(cr03);
strcat(cr02,cr03);
int crLength = strlen(cr02);
printf("strcopy()函数处理的结果:");
puts(cr02);
printf("总的字符串长度:%d",crLength);

image9541f35e68aff011.png

五、指针

  • 说某个变量的地址时,说的都是这个变量的起始地址。

(一)指针的本质和使用

1
2
3
4
5
6
7
int a = 10;
//定义指针变量
int *aPointer;
//给指针变量赋值
aPointer = &a;
//输出指针变量所指的内容
printf("aPointer所指的内容为:%d",*aPointer);

imagea2700a12e3e9c9eb.png

1、本质:就是新建一个变量用来存放另一个变量的内存地址。

  • &

    取地址符,也称引用,用它可以获取某个变量的内存地址。

    1
    2
    int a = 10;
    printf("%d",&a);

    imaged91a9c8d83f30db6.png

  • *

    取值操作符,也称解引用,用它可以得到一个地址对应的数据。

    1
    2
    3
    int a = 10;
    int *aPointer = &a;
    printf("“*aPointer”输出为:%d;\n“aPointer”输出的内容为:%d",*aPointer,aPointer);

    image99c2127aebde5a5a.png

    注:声明指针变量时,星号*位于原变量名前连着或紧跟在类型声明符后面或和二者中间都有间隔,他都不会报错或编译出现问题,但是我们约定俗成的书写习惯就是让星号和原变量名连在一起,以便在声明多个变量时避免不必要的歧义!

2、使用要点

  • 指针声明:和普通变量一样,只不过指针变量名之前要加*,后续使用的时候也要加,包括输出的时候,否则不加星号直接操作的话操作的是变量中保存的地址代码!

  • 因此,int *aPointer的变量名是aPointer而不是*aPointer

  • 指针变量的数据类型不是固定的,但是声明时要和所指对象的数据类型保持一致!

  • 指针存在的意义就是为了间接访问。

  • *&的优先级相同,但同时出现时会从右向左依次结合,因此:

    1
    2
    int a = 10;
    int pointer01 = &a;

    对于如上,&*pointer01语句的含义与&a是一样的!

3、使用场景

只有两个使用场景:传递与偏移

(1)传递

因为C语言的函数调用时传递参数,是值传递,实参赋值给形参!

所以在需要我们通过另一个函数改变其他函数的值的时候,传递参数就用指针,给原函数的某变量声明个指针再把该指针传给其他参数。

1
2
3
4
5
6
7
8
9
10
void change01(int *b){
*b = 100;
}

int main() {
int a = 1;
change01(&a);
printf("a=%d",a);
return 0;
}

image80292f5587156cc9.png

(2)偏移
  • 对指针进行加减运算称为指针的偏移,加就是向后偏移,减就是向后偏移。

  • 指针所指向的地址实际上是内容中第一个字符所在的地址。

  • 源码中指针每次加或减1,实际在他存储的内容中是加或减了他类型的一个基数大小:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    int a = 1;
    int *b = &a;
    printf("原先b存储的地址内容:%d\n",b);
    b += 1;
    printf("b += 1后存储的内容:%d\n",b);

    /*
    此处指针b是int类型,int类型大小是4个字节,所以b加或减1后实际存储的地址数据是加或减了4
    */

    imagee61bbb280b70d5c5.png

  • 有了这特性我们可以利用指针的偏移来进行数组遍历的另一种方式:

    • 顺序输出

      1
      2
      3
      4
      5
      6
      7
      8
      9
      //声明数组
      int bb[5] = {2,3,1,6,3};
      //创建数组的指针。因为数组名本身就是指针,所以不用取地址。
      int *p01 = bb;
      printf("int数组长度:5\n\n");
      //遍历并输出数组
      for(int i = 0; i < 5; i++){
      printf("00%d\n",*(p01+i));
      }

      imagead2d15cfeaf7546b.png

    • 逆序输出

      1
      2
      3
      4
      5
      //遍历并输出数组
      p01 += 4;
      for(int i = 0; i < 5; i++){
      printf("00%d\n",*(p01-i));
      }

      imaged53d37c3bf92c8dc.png

    (3)指针与一维数组

    数组名作为实参传递给函数时,是弱化为指针的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void change01(char *a){
    *a = 'R';
    a[1] = 'O'; //与“*(a+1)”等价
    *(a+2) = 'M';
    }

    int main() {
    char str01[15] = "romantic";
    change01(str01);
    puts(str01);
    return 0;
    }

    image2475629ce1ce49bc.png

(二)指针与动态内存申请——malloc()free()

非常重要!考研初试复试都会考!

  • 定义数组时要确定数组长度,不方便,这是因为C语言的基本类型变量都存储在栈空间中,编译时就确定了。
  • 如果要动态地使用内存,就要使用堆内存的方式储存内容。
  • malloc函数的作用是动态分配内存,以解决静态内存定长、不能手动释放等缺陷。

1、malloc()函数的使用

1
2
3
#include <stdlib.h>

void* malloc(size_t size);
  • 需引入头文件#include <stdlib.h>

  • 返回值为一个void类型的指针,指向刚申请的空间的地址。

    C/C++规定,void*类型可以强制转换为任何其它类型的指针。

    因此我们需要对返回值进行强制类型转换,参考如下代码。

  • 形参只能有一个,作用是设定你打算开辟空间的大小(字节),int类型整数。

实例参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
//用int类型变量存储我们需要开辟的内存空间大小(字节)的值
int needSize = 15;
//定义一个指针用来存储malloc()开辟出空间后返回的地址
char *ptr01;
//所需开辟字节数量needSize作为malloc()的参数
//用(char*)对返回值进行强制类型转换,然后赋值给我们定义的指针,以便进行后续操作
ptr01 = (char*)malloc(needSize);
strcpy(ptr01,"Hallo Java!");
// ptr01 = "Hallo Java!"; //这里不能用这条语句,不知道为什么
puts(ptr01);
return 0;
}

image9ae92acdd6301e5f.png

  • 如果实际存储的内容的字节大小大于给malloc()传入的参数的大小,那么多出来的内容会写到别的存储单元里!

2、free()函数的使用

  • 用于释放malloc()开辟出来的某块空间。
  • 当函数结束后,指针变量*pointor就销毁了,但是其指向的字节仍然占用,而且因为指针的销毁,就无法访问这片内存!
  • 虽然程序运行完之后所有空间都会被释放,但一般来讲我们还是要要手动释放空间,以避免程序或系统崩溃。

使用方式:

1
2
3
4
free(原指针名);

//例如:
free(ptr01);

注:传给的参数必须是malloc()返回的指针,如果对其进行了偏移运算那么会报错!

六、函数

(一)函数的声明与定义

1、声明

返回值类型 + 函数名 + [形参列表] + 分号:

1
void fName(int a, float b, char c);

2、定义/实现

在定义了的基础上再写好函数体就可以了。

1
2
3
void fName(){
printf("hahaha");
}

3、使用说明

  • C语言中我们习惯——函数先声明再调用!

    即:默认情况下,只有后面定义的函数才可以调用前面定义过的函数!

    C语言的函数可以只声明不定义(或者说实现):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include <stdio.h>

    //只声明不实现:
    void funA();

    main(){
    ......
    }

    //后边再实现:
    void funA(){
    ......
    }
  • 与Java不同,C的函数没有那些权限修饰符。

  • 函数不用显式声明,隐式声明的函数默认返回值类型是 int

  • C语言规定函数不能嵌套定义:

    1
    2
    3
    4
    void a(){
    //函数里面不能再声明函数
    void b(){}
    }
  • 函数可以嵌套调用,但不能调用main()函数。

(二)函数的分类与调用

1、递归调用

//初试必考!

  • 函数自己调用自己的行为称为递归。递归函数一定要有结束条件,不然会陷入死循环!

  • 例:使用递归求n的阶乘:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    int digui(int a){
    if(a == 1){
    return 1;
    }
    return a * digui(a-1);
    }


    int main() {

    //使用递归求n的阶乘
    int a;
    printf("请输入需要求其阶乘的数,按回车开始:\n");
    scanf("%d",&a);
    printf("计算已开始......\n");
    printf("计算结果为:%d",digui(a));
    }

    imagebee2cc0db53c6f41.png

(三)自定义用户类库

1、头文件的引入

系统自带的类库,使用#include <>即可,二用户自定义的类库,则把尖括号改为双引号并且将头文件与源码放在同一路径下!

1
2
#include <stdio.h>
#include "myJava01.h"

(四)局部变量和全局变量

  • 定义在函数外就是全局变量,函数内就是局部变量。
  • 全局变量如果与局部变量重名,那么就近原则。
  • 局部变量只在自己所在代码块内有效,循环体内的局部变量在循环体外也无法访问!
  • C语言的全局变量不存储在堆内存或栈内存内,而是在“数据段”中。
  • C语言中不建议使用全局变量,甚至有些公司禁止使用全局变量。(但是初试怎么方便怎么来,这里没要求)
  • 全局变量实际上只有在被定义后才有效,在被定义之前也是不存在的!
  • 函数形参中声明的变量也是局部变量,在函数没有被调用的时候它形参中的局部变量也是没有被分配内存单元的。
  • 全局变量在整个程序执行的全程中都占用内存单元。
  • C语言要求把程序中的函数做成一个封闭体,除“实参→形参”的渠道与外界发生联系外,没有其他渠道。

七、结构体与C++引用详解

//初试用到结构体的概率很高的

//选择题会考结构体对齐

(一)什么是结构体

  • 结构体就是用来存储不同类型数据的集合。

  • 就像做了个模板,模板里面有各种属性,用的时候按照这个模板定义一些”结构体“。

  • 我们把结构体声明在函数外,而在需要使用的函数内实现并使用。

1、结构体的声明

一般形式为:struct 结构体名 {成员表列};

  • 不管是声明还是定义,结尾都必须加分号!
1
2
3
4
5
6
7
8
9
//定义结构体
struct studentsInfo {
int num;//学号
char name[10];//姓名
char sex;//性别
int age;//年龄
float score;//分数
char addr[30];//地址
};

2、结构体的定义(实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//定义结构体
struct studentsInfo {
int num;//学号
char name[10];//姓名
char sex;//性别
int age;//年龄
float score;//分数
char addr[30];//地址
};

int main() {
//结构体实现
struct studentsInfo stu01 = {
101107, "Gatsby", '1', 22, 61.0, "浙江省枝江市***"
};
printf("输出结构体内容:\n学号:%d; 姓名:%s; 性别:%c; \n年龄:%d; 成绩:%5.2f; 地址:%s。\n",
stu01.num, stu01.name, stu01.sex, stu01.age, stu01.score, stu01.addr);
}

image.png

3、结构体数组

  • 实现结构体的时候,将其定义为数组变量,那么它就成了一个结构体数组:

    1
    struct studentsInfo sAr[3];

    这时候它里面储存的就是三个结构体数组,我们可以对其循环遍历访问和操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    //定义结构体
    struct studentsInfo {
    int num;//学号
    char name[10];//姓名
    char sex;//性别
    int age;//年龄
    float score;//分数
    char addr[30];//地址
    };

    int main() {
    //定义结构体数组
    struct studentsInfo sAr[3];
    printf("整个结构体占用空间大小为:%d字节。\n",sizeof(sAr));
    //循环遍历——输入内容
    for(int i = 0; i < 3; i++){
    printf("请输入第%d组数据:\n",i+1);
    //注意此处:输入的数字类型的数据仍是需要取地址的!!!
    int j = scanf("%d %s %c %d %f %s",&sAr[i].num,sAr[i].name,&sAr[i].sex,&sAr[i].age,&sAr[i].score,sAr[i].addr);
    printf("第%d组中已成功输入%d项数据;\n",i+1,j);
    }
    //循环遍历——输出内容
    for(int i = 0; i < 3; i++){
    printf("这是第%d组数据:\n", i+1);
    printf("学号:%d 姓名:%s 性别:%c 年龄:%d 分数:%f 地址:%s\n",
    sAr[i].num,sAr[i].name,sAr[i].sex,sAr[i].age,sAr[i].score,sAr[i].addr);
    }
    printf("第一个结构体数组占用空间大小为:%d字节。\n",sizeof(sAr[0]));
    printf("整个结构体占用空间大小为:%d字节。\n",sizeof(sAr));
    }

    image6b43c856e7f90c7e.png

(二)结构体的对齐

1、什么是”结构体对齐“?

我们都知道,数据是以字节形式存储的,比如一个int类型的变量就占用4个字节的空间。

而结构体是很多不同类型数据的集合体, 那它里面的这些不同类型的多个数据可不一定是一个个紧挨着存储的,它是按照特定大小的字节倍数存放的,什么倍数:

最大成员的整数倍!

即:结构体里面所有基本数据类型中,占用空间最大的那个数据类型的字节数就是该倍数。

但是如果两个连在一起的小的数据加起来**<=最大成员的整数倍,那么他们会共同占用一个最大成员的整数倍**:

比如sAr[0]中,成员有intcharfloat类型的数据,int型数据占四个字节,float四个,char一个。而数组大小是我们定义的:

1
2
3
4
5
6
7
8
struct studentsInfo {
int num;//学号
char name[10];//姓名
char sex;//性别
int age;//年龄
float score;//分数
char addr[30];//地址
};

变量char name占用10个字节,空出来的两个字节可以容得下char sex,所以他俩共同占用12个字节,那么整个数组大小为4+12+4+4+32=56个字节,整个结构体大小为56*3=168字节:

image2e15166128eb0a3b.png

2、结构体指针

(1)结构体指针

与普通指针一样,我们可以定义结构体类型的指针,用来指向原结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//声明结构体
struct std{
int num;
char name[10];
char sex;
};

void main(){
//实现结构体
struct std stdInfo = {07,"jiaran",'1'};
struct std stds[2];
//声明结构体指针并赋值
struct std *p = &stdInfo;
}
  • 结构体指针只能赋已有结构体的值。
(2)通过指针访问结构体元素

两种访问方法:

①传统的结构体名.元素名

如下列代码中的第8行

第8行中,使用(*p).num是因为点运算符.比指针运算符*优先级高!

②使用指针名->元素名访问

如下列代码中的第10行

1
2
3
4
5
6
7
8
9
10
//实现结构体
struct std stdInfo = {07,"jiaran",'1'};
//声明结构体数组的指针并赋值
struct std *p = &stdInfo;
//通过结构体指针访问结构体:
//1.传统方式访问并赋值:
printf("请输入数据:\n");
scanf("%d %s %c",&(*p).num,(*p).name,&(*p).sex);
//2.`结构体名->元素名`方式访问并输出
printf("存入的数据为:\t%d\t%s\t%c\n",p->num,p->name,p->sex);

image770fd2bfa94ec0a8.png

  • 学习和工作中我们都是用第二种方法来访问和使用,不建议使用第一种方法。

(三)typedef的使用

typedef是在C和C++编程语言中的一个关键字。作用是为现有的数据类型(intfloatchar……)创建一个新的名字,目的是为了使代码方便阅读和理解。

相当于提前定义好某个变量再起个别名,用的时候直接用这个别名去声明新的数据,改的时候改一处即可。

语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//结构体定义
typedef struct member {
int a, b;
char c;
}mbr, *mbrp; //给该结构体起的别名为mbr,给该结构体类型的指针起别名为mbrp;

//别名使用
mbr m1; //定义结构体变量;
m1.a = 10; //实现其内容
m1.b = 20;
m1.c = 't';

mbrp mp1; //定义该结构体类型的指针(此时该指针没有指向任何具体对象);
mp1 = &m1; //该指针指向结构体变量;
  • 使用方法:

    在头文件下面提前定义。

    结尾必须加分号。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include <stdio.h>

    //定义一个int型的数据
    typedef int NUM01;

    //定义结构体的别名
    typedef struct stu {
    int a;
    char b;
    char c[10];
    } stus, *stdP;

    void main(){
    //使用结构体别名定义结构体
    stus std01 = {0};//相当于struct stu std01 = {0};
    stdP ptr01 = &std01;//相当于struct stu *ptr01 = &std01;
    //使用别名定义int型数据
    NUM01 a = 10;//相当于int a = 10;
    }

(四)C++引用

1、说明

  • C++是完全兼容C的,也就是说C++文件里写C的代码是完全能跑成功的。
  • 而C++有一些特性是C没有的,所以我们可以在结合二者以应对后续的课程。

2、指针引用的使用

(1)子函数内修改主函数的普通变量
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

void change(int &i){ //这里的&在C++里叫做“引用”!且引用符必须和变量名紧邻!
i++;
}

int main() {
int a = 12;
change(a);
printf("%d",a);
}

image1dadc74cdb67bd7d.png

(2)子函数内修改主函数的一级指针变量(这是重要的!)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

void change(int * &q01, int *q02){
q01 = q02;
}

int main(){
//定义空指针变量*p01
int *p01 = NULL;
int a = 10;
int *p02 = &a;
//调用函数改变指针*p01的内容
change(p01,p02);
//输出验证
printf("改变之后:%d\n",*p01);
}

image8ed1784b72c3b88f.png

如果这里我们用纯C代码实现,那就要用到二级指针,上述代码等价于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

void change(int **q01, int *q02){
*q01 = q02;
}

int main(){
//定义空指针变量*p01
int *p01 = NULL;
int a = 10;
int *p02 = &a;
//调用函数改变指针*p01的内容
change(&p01,p02);
//输出验证
printf("改变之后:%d\n",*p01);
}

而考研初试二级指针是用不到的。

3、布尔类型

C语言没有布尔类型,我们把C++的布尔类型引用进来。

true是0,false是1。

声明:

1
bool a = true, b = false;

验证:

1
2
3
4
5
6
#include <stdio.h>

int main(){
bool a = true, b = false;
printf("%d%d",a,b);
}

image07d9a29ae892912d.png

2023.03.11.18:53

评论