C语言基础

第一章

1.C语言学习中的重难点

  • 运算符:自增、自减运算符(重点)
  • 进制:各种进制转换、原码、反码、补码
  • 数组:一维数组(重点)、二位数组、多维数组
  • 循环:多重循环的嵌套、排序、查找(重点)
  • 函数:递归函数、递归调用
  • 指针:一级指针(重点)、多级指针、指针和数组、函数、结构体之间的关系
  • 内存管理:C语言的内存管理问题、内存泄露、野指针
  • 有参宏及条件编译(重点)
  • 多文件开发:多文件编译、多文件开发(重点中的重点)

第二章

prinf函数

  • printf的格式控制的完整格式:

  • "% - 0 m.n l或h 格式字符"

  • 下面对组成格式说明的各项加以说明:

    • %:表示格式说明的起始符号,不可缺少。

    • -:有-表示左对齐输出(右侧补空格),如省略表示右对齐输出(左侧补空格)。

    • 0:有0表示指定空位填0,如省略表示指定空位不填。

    • m.n:m指域宽,即对应的输出项在输出设备上所占的字符数。N指精度。用于说明输出的实型 数的小数位数。对数值型的来说,未指定n时,隐含的精度为n=6位。

    • l或h:l对整型指long型,对实型指double型。h用于将整型的格式字符修正为short型。

    • 格式字符(格式字符用以指定输出项的数据类型和输出格式)

    • d格式:用来输出十进制整数。有以下几种用法:

    • %d %hd %ld

    • o格式:以无符号八进制形式输出整数

    • x格式:以无符号十六进制形式输出整数

    • u格式:以无符号十进制形式输出整数

    • c格式:输出一个字符

    • s格式:用来输出一个串。有几中用法


prinf函数的域宽问题

  • %d:按整型数据的实际长度输出。如果想输出指定宽度可以指定域宽
  • %md-->m域宽,打印出来以后,在控制台上,显示m位
    • 如果我们要打印的数的位数如果超过我们设定 m 则原样输出
    • 如果我们要打印的数的位数如果小于我们设定的位数,则补空白,具体如下:
    • 如果m为正数,则左对齐(左侧补空白)
    • 如果m为负数,则右对齐(右侧补空白)
    // 如果m为负数,则右对齐(右侧补空白)
    printf("|%-5d|",88);
输出结果:|88   |

转义字符问题

  • 如果想输出字符"%",则应该在“格式控制”字符串中用连续两个%表示
printf("%f%%", 1.0/3);
输出结果: 0.333333%。
\n 换行,相当于敲一下回车。
\t 跳到下一个tab位置,相当于按一下键盘上的tab键。 \b 退格,相当于按一下backspace。
\r 使光标回到本行开头。
\f 换页,光标移到到下页开头。
\\ 输出\字符,也就是在屏幕上显示一个\字符。
\' 输出'字符,也就是在屏幕上显示一个'字符。
\" 输出"字符,也就是在屏幕上显示一个"字符。

第三章

1.类型转换

  • 类型转换分为:
    • 隐式数据类型转换
    • 显示数据类型转换
  • 自动转换(隐式转换):

  • 自动转换发生在不同数据类型的量混合运算时,由编译系统自动完成。

  • 自动转换遵循以下规则:

    • 相同数据类型的值才能进行运算(比如加法运算),而且运算结果依然是同一种数据类型。系统会自动对占用内存较少的类型做一个“自动类型提升”的操作
    • 若参与运算量的类型不同,则先转换成同一类型,然后进行运算。
    • 转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转 成long型后再进行运算。
    • 所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成 double型,再作运算。
    • char型和short型参与运算时,必须先转换成int型。
    • 在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换为左边量的类型。如果右边量的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度,丢失的部分 按四舍五入向前舍入。
  • 强制类型转换(显示转换):

  • 强制类型转换是通过类型转换运算来实现的

    • 其一般形式为:(类型说明符) (表达式)
  • 强制类型转换注意点

    • 将大范围的数据赋值给小范围变量时,系统会自动做一个强制类型转换的操作,这样容易丢失精度
    • 类型说明符和表达式都必须加括号(单个变量可以不加括号),如把(int)(x+y)写成(int)x+y则成了把x转换成int型之后再与y相加了。
    • 无论是强制转换或是自动转换,都只是为了本次运算的需要而对变量的数据长度进行的临时性转换,而不改变数据说明时对该变量定义的类型。

2.sizeof 运算符

  • sizeof可以用来计算一个变量或一个常量、一种数据类型所占的内存字节数
    • 格式: 用法:sizeof(常量/变量)
    >   - 注意: sizeof不是一个函数, 是一个运算符. (面试题)
    > 

3.自增自剪

自增的两种写法

 int result = 10;
 result++;
 ++result;
 

自剪的两种写法

   result--;
    --result;
  • 自增自减写在前面和后面的区别
    • 如果++写在变量的前面, 那么会先将变量自增再用自增之后的结果参与运算
    • 如果++写在变量的后面, 那么会先将变量的值参与运算再将变量自增
**总结一句话: ++在前, 先自增再运算, ++在后, 先运算再自增**

4.逻辑运算符的短路问题

  • 与短路:&& 只要第一个条件表达为假那么后面的条件表达就不参与运算了
 int a = 10;
  int b = 20;
  int result = (a > 19) && (b++ > 6);
  printf("a = %d,b = %d, result = %d\n", a, b, result);
输出结果:a = 10,b = 20, result = 0
  • 或短路:|| 只要第一个条件表达式为真那么后面的条件表达式就不参与运算了
 int a = 10;
  int b = 20;
  int result = (a > 19) || (b++ > 6);
  printf("a = %d,b = %d, result = %d\n", a, b, result);
输出结果:a = 10,b = 21, result = 1

第四章

1.whie循环的注意点

  • 当一个变量与一个常量进行== 或 != 的时候,通常把常量写在前面
  int num = 3;
  while (3 == num) {
      printf("num = %d\n",num);
      num++;
  }

2.break关键字

  • 使用场合

    • switch
    • 循环结构
  • 注意:

    • break离开应用范围,存在是没有意义的。
```
if(1)
{
break; // 没有意义
}
```

- 在多层循环中,一个break语句只向外跳一层


```
while(1)
{
while(2)
{
  break;// 只对while2有效, 不会影响while1
}
}
```

3.continue关键字

  • 使用场合:

    • 循环结构
  • 练习: 把100~200之间的不能被3整除的数输出
for(int i = 100; i<= 200; i++)
{
  if(i %3 == 0) continue;
  printf("i = %d", i);
}

4.for循环的其他形式

  • 表达式省略(三个表达式都可以省略)

    • 如:for(; ;) 语句相当于while(1) 语句,即不设初值,不判断条件(认为表达式2为真值),循环 变量不增值。无终止地执行循环体。
  • 循环控制无关的任意表达式

    • 表达式1和表达式3可以是一个简单的表达式,也可以是逗号表达式,即包含一个以上的简单表达 式,中间用逗号间隔。
```
for(sum=0,i=1;i<=100;i++) sum=sum+i;
或
for(i=0,j=100;i<=j;i++,j--) k=i+j;

// 表达式1和表达式3都是逗号表达式,各包含两个赋值表达式,即同时设两个初值,使两个变量增值.
```

5.for嵌套实现

1.
*
**
***
****
*****

2.
*****
****
***
**
*
  • 分析

    • 应该打印5行
    • 最多打印5列
    • 每一行和每一列关系是什么? 列数<=行数
  • 实现

for(int i = 0; i< 5; i++){
        for(int j = 0; j <= i; j++){
            printf("*\t");
        }
        printf("\n");
    }
    for(int i = 0; i< 5; i++){
        for(int j = i; j < 5; j++){
            printf("*\t");
        }
        printf("\n");
    }
  • 规律
    • 尖尖朝上,改变内循环的条件表达式,让内循环的条件表达式随着外循环的i值变化
    • 尖尖朝下,改变内循环的初始化表达式,让内循环的初始化表达式随着外循环的i值变化

第五章

1.递归函数

  • 自己调用自己
  • 存在一个条件能够让递归结束
  • 问题的规模能够缩小
void getNumber2()
{
    int number = -1;
    printf("请输入一个正数abc\n");
    scanf("%d", &number);
    if (number < 0) {
//        负数
        getNumber2();
    }else{
//        正数
       printf("number = %d\n", number);
    }
}
  • 能用循环实现的功能,用递归都可以实现
  • 递归就是自己搞自己,性能差
  • 注意:递归一定要有一个明确的结束条件,否则会造成死循环

2.用递归法求n的阶乘

  • 分析
4!=4*3*2*1
      =4*3!
      =4*3*2!
      =4*3*2*1!

    n!=(n-1)!*n;
    (n-1)!=(n-2)!*(n-1);
    ... ...

    1!=1; 作为递归的结束条件

3.Xcode运行原理

  • 当我们点击运行后xcode自动帮我们做如下事情:
    • 编译--->.o(目标文件)--->链接--->.out 执行

4.常见的UNIX命令

  • Mac系统采用的是UNIX文件系统,所有的文件都放在根目录/下面,因此没有Windows中分C盘、D盘 的概念
  • 因为Mac系统是基于UNIX系统的,因此可以在“终端”中输入一些UNIX指令来操作Mac系统 常用的UNIX指令:(需要经常使用才不容易忘记)
ls :列出当前目录下的所有内容(文件\文件夹) pwd :显示出当前目录的名称
cd :改变当前操作的目录
who :显示当前用户名
clear :清除所有内容
mkdir : 创建一个新目录
rm: 删除文件
rm -r: 删除文件夹 -f 强制删除
touch: 创建文件
vi /open:打开、创建文件
  -q 退出
  -wq 保存并退出
  -q!强制退出
  i 进入编辑模式
  esc 退出编辑模式
  :wq!
cat/more 都可以查看文件内容

5.手动编译的步骤

  • 1)打开终端(实用工具----->终端)
  • 2)找到源文件所在的位置
  • 3)开始编译:
  • 命令: cc -c main.c
  • 4)链接
  • 命令: cc main.o
  • 5)执行
  • 执行文件命令:./a.out

第六章

1.进制

  • 二进制 0、1 逢二进一

    • 书写形式:需要以0b或者0B开头,比如0b101
  • 八进制 0、1、2、3、4、5、6、7 逢八进一

    • 书写形式:在前面加个0,比如045
  • 十六进制 0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F 逢十六进一

    • 16进制就是逢16进1,但我们只有0~9这十个数字,所以我们用A,B,C,D,E,F这五个字母来分 别表示10,11,12,13,14,15。字母不区分大小写。
    • 书写形式:在前面加个0x或者0X,比如0x45

    8 进制转 10 进制

  • 方法:

    • 每一位八进制位的值 * 8的(当前二进制进制位索引)
    • 索引从0开始, 从右至左
    • 将所有位求出的值相加
***口诀:不看你怎么存,只看你怎去取***

2.原码反码补码

  • 正数:3码合一。 对于负数:反码==除符号位以外的各位取反。补码=反 码+1)

3.位运算

  • 位运算都是针对二进制的

    • 1.& 按位与
    • 特点:只有对应的两位都是1才返回1 否则返回0
    • 口诀: 一假则假
    • 规律:任何数按位与上1结果还是那个数
* 2.| 按位或
* 特点:只要对应的两位其中一位是1就返回1
* 口诀:一真则真


*  3.^ **按位异或**
*  特点:对应的两位不相同返回1 相同返回0


```
1001
 ^ 0101
 _______
 1100
 
 //     **多个整数按位异或的结果和顺序无关**
 1001
 ^ 0101
 _______
 1100
 
 1100
 ^ 0110
 _______
 1010
 
 1001
 ^ 0110
 _______
 1111
 
 1111
 ^ 0101
 _______
 1010
 
 
 //     **相同整数按位异或结果是0**
 1001
 ^ 1001
 _______
 0000
 
 //     **任何整数按位异或上0结果不变**
 1001
 ^ 0000
 _______
 1001
 
 //     **任何整数按位异或上另一个整数两次结果还是那个数**
 1001
 ^ 1001
 ____________
 0000
 
 0000
 ^0101
 ______
 0101
     
```    
  • 4.~ 按位取反
    • 特点: 0变1 1变0

<< 左移

 a << n 把整数a的二进制位往左边移n位
 移出的位砍掉,低位补0, 发现左移会把原有的数值变大
 9 << 1 = 18  9 * 2(1) = 18
 9 << 2 = 36  9 * 2(2) = 26
 9 << n =  9 * 2(n)
 左移的应用场景:当要计算某个数乘以2的n次方的时候就用左移,效率最高
 
 0000 0000 0000 0000 0000 0000 0000 0000
 100 0000 0000 0000 0000 0000 0000 10010
 
 注意点:左移有可能改变数值的正负性

>> 右移

 a >> n 把整数a的二进制位往右边移n位
 移出的位砍掉, 缺少的以为最高位是0就补0是1就补1(是在当前操作系统下)
 9 >> 1 = 4  9 / 2(1) = 4
 9 >> 2 = 2  9 / 2(2) = 2
 右移的应用场景:当要计算某个数除以2的N次方的时候就用右移,效率最高
 0000 0000 0000 0000 0000 0000 0000 0000
 000000 0000 0000 0000 0000 0000 0000 10

4.数组

1.数组内部存储细节

  • 存储方式:

    • 1)计算机会给数组分配一块连续的存储空间
    • 2)数组名代表数组的首地址,从首地址位置,依次存入数组的第1个、第2个....、第n个元素
    • 3)每个元素占用相同的字节数(取决于数组类型)
    • 4)并且数组中元素之间的地址是连续。

2.数组的地址

  • 在内存中,内存从大到小进行寻址,为数组分配了存储空间后,数组的元素自然的从上往下排列 存储,整个数组的地址为首元素的地址。
    • 数组a的地址是ffc1,a[0]的地址是ffc1,a[1]的地址是ffc5
    • 因此a == &a[0],即第一个元素的地址就是整个数组的地址
   // 注意: 由于内存寻址是从大到小, 所以存储数据也是从大到小的存储(先存储二进制的高位, 再存储低位)
    //  高位   -->                    低位
    // 00000000 00000000 00000000 00001001

第七章

1.数组练习

  • 要求从键盘输入6个0~9的数字,排序后输出

(填坑法)

 // 1.定义数组保存用户输入的数据
    int nums[10] = {0};
    // 2.接收用户的数据
    int value = -1;
    for (int i = 0; i < 6; i++) {
        printf("请输入第%i个数据\n", i + 1);
        scanf("%i", &value); // 2, 2, 1, 2
        // 7, 3, 6, 1
//        nums[value] = 1;
        nums[value] = nums[value] + 1;
    }
    
    for (int i = 0; i < 10; i++) { // i == 7
//        printf("nums[%i] = %i\n", i , nums[i]);
        /*
        if (nums[i] != 0) {
            printf("%i\n", i); // 1, 2, 2, 2
        }
         */
        for (int j = 0; j < nums[i]; j++) { // j == 1
            printf("%i\n", i); // 1, 1, 2, 3, 3, 6
        }
    }

  • 数组的选择排序
  • 已知一个无序的数组, 里面有5个元素, 要求对数组进行排序


int nums[8] = {99, 12, 88, 34, 5, 44, 12, 100}
int length = sizeof(nums) / sizeof(nums[0]);
    printf("length = %i\n", length);
    for (int i = 0; i < length; i++) {
        printf("nums[%i] = %i\n", i, nums[i]);
    }
    
    // length - 1是为了防止角标越界
    // length - 1因为最后一个元素已经没有可以比较的了
    // 0, 1, 2, 3, 4
    for (int i = 0; i < length - 1; i++) {
        for (int j = i+1; j < length; j++) {
//            printf("*");
//            printf("i = %i, j = %i\n", i, j);
            if (nums[i] > nums[j]) {
                int temp = nums[i];
                nums[i] = nums[j];
                nums[j] = temp;
            }
        }
//        printf("\n");
    }
    
    printf("--------------\n");
    for (int i = 0; i < length; i++) {
        printf("nums[%i] = %i\n", i, nums[i]);
    }

  • 数组的冒泡排序
/*
     思路: 
     1.先分析如何比较
     2.找出比较的规律比较完一次之后第二次比较会少一次
     3.打印倒三角
     4.打印需要比较的角标
     5.比较并交换位置
     6.将常量替换为变量(length)
     */
    // 已知一个无序的数组, 里面有5个元素, 要求对数组进行排序
    int nums[6] = {99, 12, 88, 34, 5, 7};
    int length = sizeof(nums) / sizeof(nums[0]);
    for (int i = 0; i < length; i++) {
        printf("nums[%i] = %i\n", i, nums[i]);
    }
    for (int i = 0; i < length - 1; i++) {
        for (int j = 0; j < length - 1 - i; j++) {
//            printf("*");
//            printf("%i == %i\n", j, j+1);
            if (nums[j] > nums[j + 1]) {
                int temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
            }
        }
//        printf("\n");
    }
    printf("----------\n");
    for (int i = 0; i < length; i++) {
        printf("nums[%i] = %i\n", i, nums[i]);
    }

  • 数组的折半查找

    • 方法1
```
nt min, max, mid;
    min = 0;
    max = length - 1;
    
    // 只要还在我们的范围内就需要查找
    while (min <= max) {
        // 计算中间值
        mid = (min  + max) / 2;
        if (key > nums[mid]) {
            min = mid + 1;
        }else if (key < nums[mid])
        {
            max = mid - 1;
        }else
        {
            return mid;
        }
        
    }
    return -1;
```

* 方法2

    
```

int min, max, mid;
min = 0;
max = length - 1;
mid = (min + max) / 2;

while (key != nums[mid]) {
    // 判断如果要找的值, 大于了取出的值, 那么min要改变
    if (key > nums[mid]) {
        min = mid + 1;
    // 判断如果要找的值, 小雨了取出的值, 那么max要改变
    }else if (key < nums[mid])
    {
        max = mid - 1;
    }
    
    // 超出范围, 数组中没有需要查找的值
    if (min > max) {
        return -1;
    }
    // 每次改变完min和max都需要重新计算mid
    mid = (min + max) / 2;
}

// printf("aaaaaa\n");

return mid;

```
  • 进制查表法

    // 转换所有的进制
    // value就是需要转换的数值
    // base就是需要&上的数
    // offset就是需要右移的位数  
    // 1.定义一个数组, 用于保存十六进制中所有的取值
    char charValues[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    // 2.定义一个数组, 用于保存查询后的结果
    char results[32] = {'0'};
    // 3.定义一个变量, 用于记录当前需要存储到查询结果数组的索引
    int pos = sizeof(results)/ sizeof(results[0]);
    
    while (value != 0) {
        // 1.取出1位的值
        int res = value & base;// 1 7 15
        // 2.利用取出来得值到表中查询对应的结果
        char c = charValues[res];
        // 3.存储查询的结果
        results[--pos] = c;
        // 4.移除二进制被取过的1位
        value = value >> offset;// 1 3 4
    }
    
    // 4.打印结果
    for (int i = pos; i < 32; i++) {
        printf("%c", results[i]);
    }
    printf("\n");

第八章

1.字符串

  • 字符串是位于双引号中的字符序列

    • 在内存中以“\0”结束,所占字节比实际多一个
  • 使用的格式字符串为“%s”,表示输入、输出的是一个字符串 字符串的输出

  • 输出

    *%s的本质就是根据传入的name的地址逐个去取数组中的元素然后输出,直到遇到\0位置

char ch[] = "lnj";
printf("%s\n", ch);

\0引发的脏读

char name[] = {'c', 'o', 'o', 'l' , '\0'};
char name2[] = {'l', 'n', 'j'};
printf("name2 = %s\n", name2);
输出结果: lnjcool
  • 注意点:
  • 需要明白的一点就是字符串以\0结尾, 没有\0就不是字符串
  • 只要是用双引号括起来的都是字符串
  • 字符串的本质就是数组

2.指针与数组

  • 在指针指向数组元素时,允许以下运算:

    • 加一个整数(用+或+=),如p+1
    • 减一个整数(用-或-=),如p-1
    • 自加运算,如p++,++p
    • 自减运算,如p--,--p
      • 如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素,p-1指向同 一数组中的上一个元素。
  • 结论:

    • 引用一个数组元素,可用下面两种方法:
      • 下标法,如a[i]形式
      • 指针法, *(p+i)形式
      • 注意:
  • 数组名虽然是数组的首地址,但是,数组名所所保存的数组的首地址是不可以更改的

  • 两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。

    • 实际上是两个指针值(地址)相减之差再除以该数组元素的长度(字节数)。
    • (pointer2地址值 -pointer地址值) / sizeof(所指向数据类型)
  • 注意:

    • 指针之间可以相减,但不可以相加(相加无意义)
  • 只要一个指针指向了数组, 那么访问数组就有3种方式:
      1. : ages[0];
      1. : p[0];
      1. : *(p + 0);
  • 保存字符串的两种方式:
    • char str[] = "lnj";

      • 存储的位置: 栈
      • 特点: 相同的字符串会重复的分配存储空间
      • 字符串可以修改
    • char *str = "lnj"

      • 存储的位置: 常量区
        • 特点: 相同的字符串不会重复的分配存储空间
        • 字符串不可以修改

3.字符串指针

  • 1、使用字符数组来保存的字符串是保存栈里的,保存栈里面东西是可读可写,所有我 们可以改变里面的字符当把一个字符串常量赋值一个字符数组的时候,那么它会把字符串常量 中的没有字符都放到字符数组里面

  • 2、使用字符指针来保存字符串,它保存的是字符串常量地址,常量区是只读的,所 以我们不可以修改字符串中的字符

  • 注意
    不能够直接接收键盘输入

char *str;
scanf("%s", str);

错误的原因是:str是一个野指针,他并没有指向某一块内 存空间,所以不允许这样写如果给str分配内存空间是可以这样用 的

4.指向函数的指针

  • 由于这类指针变量存储的是一个函数的入口地址,所以对它们作加减运算(比如p++)是无意义的
  • 函数调用中"(指针变量名)"的两边的括号不可少,其中的不应该理解为求值运算,在此处它 只是一种表示符号

int sum(int v1, int v2)
{
    return v1 + v2;
}

int minus(int v1, int v2)
{
    return v1 - v2;
}

// 让demo接受一个指向函数的指针
// 以后我们只需要给demo函数传递对应的指针, 那么函数内部就可以调用不同的函数


int demo3(int v1, int v2, int (*p)(int, int))
{
    return p(v1, v2);
}
int main(int argc, const char * argv[]) {
    // 定义一个方法, 给你两个数, 用户要求你做加法你就做加法, 用户要求你做减法, 那你就做减法
    
    printf("mins = %i\n", demo3(20, 10, minus));
    printf("sum = %i\n", demo3(20, 10, sum));
    
    return 0;
}

第九章

1.构造类型

  • 构造数据类型:构造数据类型是根据已定义的一个或多个数据类型用构造的方法来定义的。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基 本数据类型或又是一个构造类型。

  • 在C语言中,构造类型有以下几种:

  • 数组类型

  • 结构体类型

  • 共用体(联合)类型

2.结构体

  • 注意:
    • struct Dog sd2;
    • 特别注意: 结构体和数组有一点区别, 数组不能先定义再进行一次性的初始化, 而结构体可以
    • 只不过需要明确的告诉系统{}中是一个结构体
      • sd2 = (struct Dog){"xq", 8, 8.8}; // 数组? 结构体?
* 指定将数据赋值给指定的属性
    * struct Dog sd3 = {.height = 1.77, .name = "ww", .age = 33};
  • 1.定义结构体类型并不会分配存储空间

  • 2.只有定义结构体变量才会真正的分配存储空间

    • 结构体第0个属性的地址就是结构体的地址
    • 和数组一样, 结构体内存寻址从大到小, 存储数组是从小到大(先存储第0个属性, 再一次存储其它属性)
  • 结构体如何开辟存储空间

    •  看上去, 结构体分配存储空间是将所有属性占用的存储空间的总和加在一起后再分配
      
    •  注意: 
      
    •  其实结构体分配存储空间本质上并不是将所有属性占用的存储空间的总和加在一起后再分配
      
    •  而是会获取结构体类型中占用内存最大的属性的大小, 然后取该大小的倍数
      
    •  特例: 
      
    •  如果剩余的存储空间"不够"存储将要存储的数据, 那么就会重新开辟8个字节的存储空间, 并且将需要存储的数据放到新开辟的存储空间中
      
    •  如果剩余的存储空间"够"存储将要存储的数据, 那么就不会开辟了
      

3.结构体指针

  • 结构指针变量说明的一般形式为:
struct 结构名 *结构指针变量名
  • 示例
```
// 定义一个结构体类型
      struct Student {
          char *name;
          int age;
      };

     // 定义一个结构体变量
     struct Student stu = {“NJ", 27};

     // 定义一个指向结构体的指针变量
     struct Student *p;

    // 指向结构体变量stu
    p = &stu;

     /*
      这时候可以用3种方式访问结构体的成员
      */
     // 方式1:结构体变量名.成员名
     printf("name=%s, age = %d \n", stu.name, stu.age);

     // 方式2:(*指针变量名).成员名
     printf("name=%s, age = %d \n", (*p).name, (*p).age);

     // 方式3:指针变量名->成员名
     printf("name=%s, age = %d \n", p->name, p->age);

     return 0; }
```

4.枚举类型

枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何 基本类型。


要求定义一个枚举来保持一年四季
    // 1.定义枚举类型
    // 定义枚举类型的规范
    // 枚举类型的取值一般以k开头 后面跟上枚举类型的名称  跟上当前取值的含义
    // 和结构体一样, 枚举类型的名称首字母大写
    enum Season
    {
        kSeasonSpring,
        kSeasonSummer,
        kSeasonAutumn,
        kSeasonWinter
    };
    
    enum Gender
    {
        kGenderMale,
        kGenderFemale
    };
    
    enum Season es;
    es = kSeasonAutumn;
    
    enum Gender eg;
    eg = kGenderFemale;

第十章

1.static和extern关键字-对变量的作用

  • static 与 extern对局部变量的作用

    • 延长局部变量的生命周期,从程序启动到程序退出,但是它并没有改变变量的作用域
    • 定义变量的代码在整个程序运行期间仅仅会执行一次
  • extern用在函数内部

    • 不是定义局部变量,它用在函数内部是声明一个全局变量
  • static 全局变量的作用
* 声明一个内部变量 static int a;
* 定义一个内部变量 static int a = 10;
* 由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以 避免在其它源文件中引起错误。
  • extern:
    • 用于声明一个外部全局变量
    • 声明只需要在使用变量之前声明就可以了
  • static:
    • 用于定义一个内部全局变量
    • 声明和定义的区别:
    • 声明不会开辟存储空间
    • 定义会开辟存储空间

2.宏定义

  • 注意点:

      1. 宏名一般用大写字母,以便与变量名区别开来,但用小写也没有语法错误
    • 2)对程序中用双引号扩起来的字符串内的字符,不进行宏的替换操作
  • 注意点:

    • 1)宏名和参数列表之间不能有空格,否则空格后面的所有字符串都作为替换的字符串.
  • 宏定义在什么时候替换
    • 源代码 --> 预处理 -->汇编 -->二进制 -->可执行程序
            不能有空格
#define average (a, b) ((a+b)/2)

提前结束宏定义的作用域
//#undef COUNT

3.条件编译

  • 条件编译和选则结构if的共同点

    • 都可以对给定的条件进行判断, 添加满足或者不满足都可以执行特定的代码
  • 条件编译和选则结构if的共区别

  • 1.生命周期不同

    • if 运行时
      
    • #if 编译之前
      
  • 2.#if需要一个明确的结束符号 #endif

    • 为什么需要一个明确的结束符号?
      
    • 如果省略掉#endif, 那么系统就不知道条件编译的范围, 那么会将满足条件之后的第二个条件之后的所有内容都清除
      
  • 3.if会将所有的代码都编译到二进制中

    • if只会将满足条件的部分一直到下一个条件的部分 编译到二进制中

  • 应用场景:

    • 用于调试和发布阶段进行测试
    • 调试阶段: 程序写代码的阶段
    • 发布阶段: 上传到AppStore的阶段
#define DEBUG 1 // 0是调试阶段 1是发布阶段

#if DEBUG == 0
// 调试阶段
#define NJLog(format, ...) printf(format,## __VA_ARGS__)
#else
// 发布阶段
#define NJLog(format, ...)
#endif
  • 注意点: 条件编译不能用来判断变量, 因为不在同一个生命周期
    • 君生我未生, 我生君已老
    • 一般情况下, 条件编译是和宏定义结合在一起使用的
#ifdef SCORE // 判断是否定义了后面的宏
    printf("score\n");
#elif COUNT
    printf("count\n");
#else
    printf("OTHER\n");
#endif
     
#ifndef SCORE // 是不是没有定义名称叫做SCORE的宏
    printf("no score\n");
#else
    printf("score\n");
#endif
     */
    
    /*
#if defined(SCORE) // 判断是否定义了SCORE这个宏
    printf("score\n");
#else
    printf("no score\n");
#endif
    
#if !defined(SCORE) // 判断是否没有定义SCORE这个宏
    printf("no score\n");
#else
    printf("score\n");
#endif


4.typedef

  • 什么是typedef, 它有什么作用

    • typedef可以给一个已知的数据类型起别名 (外号)

    • 利用typedef给数据类型起别名的格式:

    • typedef 原有的数据类型 别名(外号);

  • 注意:

      1. typedef不仅能给系统原有的数据类型起别名, 也可以给一个自定义的数据类型起别名
      1. 利用typedef给数据类型起别名, 并不会生成一个新的数据类型, 仅仅是给原有的类型起了一个别名而已

注意: 如果是给指向函数的指针起别名, 那么指向函数的指针的指针名称就是它的别名

functionPotinter == int(*functionPotinter)(int , int)
typedef int(*functionPotinter)(int , int);

5.const

  • 如果const写在指针类型的左边, 那么意味着指向的内存空间中的值不能改变, 但是指针的指向可以改变

  • 如果const写在指针的数据类型和*号之间, 那么意味着指向的内存空间中的值不能改变, 但是指针的指向可以改变

  • 如果const写在指针的右边(数据类型 * const), 那么意味着指针的指向不可以改变, 但是指针指向的存储空间中的值可以改变

  • 规律:

  • 如果const写在指针变量名的旁边, 那么指针的指向不能变, 而指向的内存空间的值可以变

  • 如果const写在数据类型的左边或者右边, 那么指针的指向可以改变, 但是指向的内存空间的值不能改变

  • **const对基本数据类型的作用, 可以让基本数据类型的变量变为常量

你可能感兴趣的:(C语言基础)