初识C语言

  • 1. 什么是C语言?
  • 2. 第一个C语言程序
    • 2.1 如何写代码
    • 2.2 运行代码
  • 3. 数据类型
    • 3.1 为什么出现这么多类型?
    • 3.2 如何使用数据类型
    • 3.3 数据类型的大小
  • 4. 变量、常量
    • 4.1 定义变量的方法
    • 4.2 变量的命名
    • 4.3 全局变量和局部变量
    • 4.4 变量的使用
      • 4.4.1 scanf函数的用法
    • 4.5 变量的作用域和生命周期
    • 4.6 常量
    • 4.61 字面常量
    • 4.62 const 修饰的常变量
    • 4.63 #define定义的标识符常量
    • 4.64 枚举常量
  • 5. 字符串
      • 5.0.1 strlen函数的用法
  • 6. 字符基本介绍
    • 6.1 字符
    • 6.2 转义字符
  • 7. 注释
  • 8. 选择语句
  • 9. 循环语句
  • 10. 函数
  • 11. 数组
    • 11.1 数组的定义
    • 11.2 数组的下标
    • 11.3 数组的使用
  • 12. 操作符
    • 12.1 算术操作符
    • 12.2 移位操作符
    • 12.3 位操作符
      • 12.3.1 按位与
      • 12.3.2 按位或
      • 12.3.3 按位异或
    • 12.4 赋值操作符
    • 12.5 单目操作符
      • 12.5.1 逻辑反操作
      • 12.5.2按位取反
      • 12.5.3 前置++
      • 12.5.3 后置++
      • 12.5.4 强制类型转换
    • 12.6 关系操作符
    • 12.7 逻辑操作符
      • 12.7.1 逻辑与
      • 12.7.2 逻辑或
    • 12.8 条件操作符
    • 12.8 逗号表达式
    • 12.9 下标引用、函数调用和结构成员
      • 12.9.1 下标引用操作符
      • 12.9.2函数调用操作符
  • 13. 格式控制符
  • 14. 常见关键字
    • 14.1 关键字基本介绍
      • 14.1.1 register关键字
      • 14.1.2 typedef关键字
      • 14.1.3 static关键字
  • 15. #define定义常量和宏
    • 15.1 #define基本用法介绍
      • 15.1.1define定义标识符常量
      • 15.1.2 define定义宏
  • 16. 初探指针
    • 16.1 内存
    • 16.2 指针介绍
    • 16.3 解引用操作
    • 16.4 指针变量的大小
  • 17. 结构体

前言
本人是一位小萌新,初次学习c语言。如有错误的地方,恳请批评指正!

1. 什么是C语言?


C语言是一门通用计算机编程语言,广泛应用于底层开发。C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。

什么是底层开发呢?

简单概括来讲,底层和应用层是一个相对立的概念,底层的比较靠近计算机硬件,而应用层更靠近人类思想,更加抽象和具体。从计算机硬件往上依次还有驱动层、操作系统、应用软件、再就是用户层。如下图:


2. 第一个C语言程序

2.1 如何写代码


哈哈~ 开个玩笑让我们进入正题!

//标准写法格式
#include <stdio.h> //  #include 是预处理指令的意思 
//stdio.h中 std 是标准的意思  
//i代表的是input 输入的意思 
//o代表的是output输出的意思 结合起来是标准输入输出的意思也叫做头文件。

int main() //int是整型的意思 main()是主函数的意思,也是主程序的入口。
//这里要注意的是 main函数必须要有,并且在一个项目中main函数有且只有一个!

{  //两个花括号和其中的代码 这一部分叫做函数体  

    printf("hello world"); //printf 是print function 打印函数
             
    return 0; //返回整型的意思,return是返回的意思
    //0代表整型和main()函数前面的int相呼应。
    
}

2.2 运行代码

输入代码接下来我们就可以按Ctrl+F5(开始执行不调试)运行代码。(编译 - 链接 - 运行)得到以下输出结果:
这样我们第一个代码就完成啦!你的电脑就开始向这个世界问好了!恭喜你!


3. 数据类型

在C语言中有多种数据类型,例如:

3.1 为什么出现这么多类型?

在我们日常生活中想表达一些东西的值 ,那就需要有数据类型,比如年龄是整数、体重是小数、身高也小数。存在这么多的类型,其实是为了更加丰富的表达生活中的各种值。

3.2 如何使用数据类型

使用方法如下:

#include <stdio.h>
int main()
{
    char ch = 'a'; //字符'a' ch是变量名 将字符类型的值赋给ch
    int age = 20; //年龄是整数20 age是变量名 将整型类型的值赋给age
    float weight = 64.7 //体重是小数64.7 weight是变量名 
                        //将单精度浮点型的值赋给weight
    double Π = 3.1415926; //圆周率也是更长的小数3.1415926 Π是变量名
                          //将双精度浮点型的值赋给Π
    
    return 0;
}

3.3 数据类型的大小

首先我们需要了解计算机储存空间大小的基本单位:

bit:比特位 ,计算机最小单位 ,存储一个二进制位
Byte:字节 ,计算机的最小存储单元 ,一个字节存放8个比特位
1KB = 1024Byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB

在C语言中,我们计算一个类型创建的变量所占空间的大小,使用单目操作符sizeof(以字节为单位)。

#include <stdio.h>
int main()
{
    printf("%d\n", sizeof(char)); //%d是格式控制符 以十进制有符号的形式输出
                                  //\n是转义字符换行的意思
    printf("%d\n", sizeof(short));   //sizeof后面()不能省略掉 但是变量名可以
    printf("%d\n", sizeof(int));
    printf("%d\n", sizeof(long));
    printf("%d\n", sizeof(long long));
    printf("%d\n", sizeof(float));
    printf("%d\n", sizeof(double));

    return 0;
}

输入代码让我们运行起来!


可以看到各种类型的大小分别是:

  char所占空间是1个字节
  short所占空间是2个字节
  int是所占空间4个字节
  long所占空间是4个字节
  long long所占空间是8个字节
  float	所占空间是4个字节
  double所占空间是8个字节

注意:sizeof操作符 计算变量/类型的大小,单位是字节。

C语言标准规定:sizeof(long) >= sizeof(int),所以在不同的编译器下sizeof(long)的值是不一样的,只要保证sizeof(long)>=sizeof(int)即可。


4. 变量、常量

在我们生活当中的有些值是不变的(比如:圆周率,性别,血型等等)。
有些值是可变的(比如:年龄,体重,薪资)。
会变的值,C语言中用变量的概念来表示。不变的值,C语言中用常量的概念来表示常量。

4.1 定义变量的方法

#include <stdio.h>
int main()
{
    int age = 150;  
    age=155;          //变量可以修改
    
    float weight = 45.5f;
    char ch = 'a';
             

    int variate =0//创建一个变量
    int variate =0;   //推荐写法:数据类型 变量名 = 0 ;
    int variate;      //不推荐写法:数据类型 变量名

    return 0;
}

4.2 变量的命名

  • 只能由字母(包括大写和小写)、数字和下划线( _ )组成。
  • 不能以数字开头。
  • 长度不能超过63个字符。
  • 变量名中区分大小写的。
  • 变量名不能使用关键字。

4.3 全局变量和局部变量

#include <stdio.h>
int global = 0;    //定义在代码块{}外面的变量我们称为全局变量
int main()
{
    int local = 0;  //定义在代码块{}里面的变量我们称为局部变量
    int global = 0; //与代码块{}外全局变量名字相同但不冲突,但不建议这么写。  
    
    return 0;
}

注意:

  • 局部变量和全局变量可以同时存在 。
  • 当局部变量和全局变量的名字相同时,在函数体内部优先使用局部变量。

4.4 变量的使用

4.4.1 scanf函数的用法

#define _CRT_SECURE_NO_WARNINGS 1//在vs2022版本下使用scanf如果报错 需要引用这个定义
                                 //scanf是C语言提供的,而scanf_s不是标准C语言提供的,是vs编译器提供
#include <stdio.h>
int main()
{
    int a = 0;
    int b = 0;
    int c = 0;
    scanf("%d",&a);             //scanf函数用法:scanf("格式控制符",&取地址符 变量名)
                                //&是取地址符号,把输入的数字放在a这个变量的地址当中
	scanf("%d%d",&b,&c)//在输入多个数时,两个数之间需要用空格隔开,区分两个数字
    printf("%d",a);
    
    return 0;
}

这个时候我们运行,光标在闪烁等待我们输入。这个时候我们输入10按下回车,printf函数就将我们输入的内容给打印出来了。

接下来我们看看如何使用变量吧:

#define _CRT_SECURE_NO_WARNINGS 1  //在vs2022版本下使用scanf如果报错 需要引用这个定义
#include <stdio.h>
int main()
{
	
	int num1 = 0;
	int num2 = 0;
	int sum = 0;
	scanf("%d%d",&num1,&num2); //使用输入函数scanf
	                           //在输入多个数时,两个数之间需要用空格隔开,区分两个数字。
	sum = num1 + num2;         //计算两个数字的和,将结果赋给sum
	printf("sum = %d\n",sum);  //打印答案
	return 0;
}

这个时候我们运行,光标在闪烁等待我们输入。这个时候我们输入10,然后使用空格。将后面要输入的数字20分隔开,按下回车。printf函数就将我们输入的两个数字相加打印出来答案是sum=30。

4.5 变量的作用域和生命周期

作用域

作用域是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的而限定这个名字的可用性的代码范围就是这个名字的作用域。

  • 局部变量的作用域是变量所在的局部范围。
  • 全局变量的作用域是整个工程。

生命周期

变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段

  • 局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
  • 全局变量的生命周期是:整个程序的生命周期。


举个例子:

//局部变量的生命周期
#include <stdio.h>
int main()
{
	{
		int a = 10;  //a进入这个大括号就向内存要了4个字节存放10
    }                //出了这个范围就把内存还给系统了 也就是销毁了
	
	printf("%d\n", a); //出了作用域的局部变量 变量a生命周期结束了 会报错!
	return 0;
}

出了作用域的局部变量 变量a生命周期结束了 会报错!


然后再让们看看全局变量的声明周期:

#include <stdio.h>
int a=10;
int main()
{
    {
        printf("1.a=%d",a); //第一次打印
    }
    printf("2.a=%d",a);    //第二次打印

    return 0;
}

接下来让我们运行,看一下能不能够打印出来

我们可以看到两次都打印出来了,所以我们得到全局变量的生命周期是整个程序的生命周期。

#include <stdio.h>
extern int a=10;    //extern (声明外部符号)如果我们在变量a前面加一个extern
                    //变量a就可以在整个.C源文件下使用了 哪怕你创建了n个文件
int main()
{
    {
        printf("1.a=%d",a); //第一次打印
    }
    printf("2.a=%d",a);    //第二次打印

    return 0;
}

运行起来看一下:

所以我们得出,全局变量的作用域是整个工程!简单概括来讲,作用域决定了变量在哪里可以使用。而生命周期决定了这个变量从什么时候开始创建,到什么时候销毁。



4.6 常量

C语言中的常量和变量的定义的形式有所差异。

C语言中的常量分为以下以下几种:

  • 字面常量
  • const 修饰的常变量
  • #define 定义的标识符常量
  • 枚举常量

4.61 字面常量

#include <stdio.h>
int main()
{
    30;       //整型字面常量
    3.14;     //浮点型字面常量
    'a';      //字符字面常量
    "abcdef"  //字符串字面常量
    
     return 0;
}

4.62 const 修饰的常变量

#include <stdio.h>
int main()
{
    int num=100;
    num=200;      //我们把num这个变量改成200
    printf("%d",num);
    
     return 0;
}

代码运行一下:

我们可以看到num这个变量是可以被修改的,接下来我们使用const来修饰一下,看看最后会打印什么出来!

#include <stdio.h>
int main()
{
    const int num=100;
    num=200;          //这里我们把num这个常变量改成200 这里语法会报错
    printf("%d",num);
    
     return 0;
}

代码运行一下:

这里可以看到代码编译不通过并且报错,说明我们用const修饰之后的常变量是不能进行修改的。num变量这个时候具有一种常属性,常属性就是一种不能被修改的属性。num本质上还是一个变量,是不过被const修饰过限制了num被修改,所以我们就叫作常变量

4.63 #define定义的标识符常量


#define MAX 10000  // 不用写等于号,不用加分号。
 
int main()
{
    int n = MAX;
    printf("%d\n", n);
    MAX=2000//不能被修改,如果这样写会报错,它是个常量。
    return 0}

4.64 枚举常量

在我们日常生活当中,有些东西可以被一一列举出来。比如说三原色(红、绿、蓝),血型(A、B、AB和O),还有性别(男、女、保密)等等。这个时候C语言就支持我们创建枚举类型,创建枚举类型的关键字就是enum(枚举关键字),接下来我们创建一个枚举类型。

#include <stdio.h>
enum Sex 
{
    //进行创建
    //枚举类型中列举出的可能取值都是枚举常量
    //我们所定义的枚举常量都是默认有值的 
    //MALE默认值是0
    MALE = 3, //我们可以赋初值,最后需要加上逗号
    
    //FAMALE默认值是1
    FEMALE,   //最后需要加上逗号
    
    //SECRET默认值是2
    SECRET    //最后一个不用加逗号
};          //这里最后需要加上分号;
 
int main()
{
    enum Sex s = MALE;   //创建一个变量,未来取值可能是枚举常量
                         //s此时表示是枚举常量MALE
    
    printf("%d %d %d\n", MALE,FEMALE,SECRET);
    MALE=3;   //错误!注意在这里不可以进行修改,枚举常量也具有常属性不可修改。
    return 0;
}

枚举常量的默认值是从0开始,依次向下递增1的。

当然我们也可以给枚举常量赋初值:



5. 字符串

"hellow world"  //字符串

这种由双引号引起来的一串字符称为字符串字面值,或者简称字符串。

注意:
字符串的结束标志是一个 \0 的转义字符。
在计算字符串长度的时候 \0 是结束标志,不算作字符串内容。

#include <stdio.h>
int main()
{
	char arr1[] = { "abc"};
	char arr2[] = { 'a','b','c'};
	char arr3[] = { 'a','b','c','\0'};

	printf("%s\n", arr1);
	printf("%s\n", arr2);
	printf("%s\n", arr3);

	return 0;
}

下面我们看看打印结果为什么会这样?

打印结果:

arr1[ ] :因为里面存放的是字符串会自带一个结束标志\0,在我们打印过程中 \0 是结束标志,不算作字符串内容,所以不会被打印出来。

arr2[ ] :因为没有\0结束标志,所以它不知道什么时候停下来,在打印的过程中它会一直往后面找\0,这个时候它才会停下来,所以我们可以看到后边都是乱码。

arr3[ ] :我们自己添加了一个’\0’,所以它知道什么时候该停下来。所以打印结果为abc。

5.0.1 strlen函数的用法

strlen是一个库函数 string strlen,用来求“字符串”的长度的,统计的是字符串中‘\0’之前出现多少个字符。

#include <stdio.h>
int main()
{
	char arr1[] = { "abc" };
	char arr2[] = { 'a','b','c' };
	char arr3[] = { 'a','b','c','\0' };

	printf("%d\n", strlen(arr1));    //和单目操作符sizeof写法一样 需要用()。
	printf("%d\n", strlen(arr2));
	printf("%d\n", strlen(arr3));


	return 0;
}

运行结果:

arr1[ ] :因为里面存放的是字符串会自带一个结束标志\0,在strlen在计算过程中,只计算\0之前的字符串长度。

arr2[ ] :因为没有\0结束标志,所以它不知道什么时候停下来,在计算长度的过程中它会一直往后面找\0,直到碰到了\0,它才将\0之前的长度计算出来,所以答案是个随机值。

arr3[ ] :我们自己添加了一个’\0’,所以它知道什么时候该停下来。所以strlen计算打印结果为3。


6. 字符基本介绍

6.1 字符

在介绍转义字符之前,我们不得不先介绍下ASCII字符代码表。在计算机中所有的数据在存储和运算时都要使用二进制数表示(因为计算机用高电平和低电平分别表示1和0),例如,像a、b、c、d这样的52个字母(包括大写)以及0、1等数字还有一些常用的符号(例如*、#、@等)在计算机中存储时也要使用二进制数来表示,而具体用哪些二进制数字表示哪个符号,当然每个人都可以约定自己的一套(这就叫编码),而大家如果要想互相通信而不造成混乱,那么大家就必须使用相同的编码规则,于是美国有关的标准化组织就出台了ASCII编码,统一规定了上述常用符号用哪些二进制数来表示,如图:


6.2 转义字符

字符集为每个字符分配了唯一的编号,我们不妨将它称为编码值。在C语言中,一个字符除了可以用它的实体(真正的字符)表示,还可以用编码值表示。这种使用编码值来间接地表示字符的方式称为转义字符。如图:


#include <stdio.h>
int main()
{
    printf("%c\n",'\130');  //八进制130是十进制的88 88对应asc码表里的大写X
    printf("%c\n",'\x30');  //十六进制30是十进制的48 对应的是asc码表的0
}


7. 注释

  • 代码中有不需要的代码可以直接删除,也可以注释掉
  • 代码中有些代码比较难懂,可以加一下注释文字
  • 注释有两种风格:
    1.C语言风格的注释 /xxxxxx/
    缺陷:不能嵌套注释
  • 2.C++风格的注释 //xxxxxxxx
    可以注释一行也可以注释多行
  • 快捷键:Ctrl+K+C
#include <stdio.h>
int Add(int x, int y)
{
    return x+y;
}
/*C语言风格注释
int Sub(int x, int y)
{
    return x-y;
}
*/
int main()
{
    //C++注释风格
    //int a = 10;
    //调用Add函数,完成加法
    printf("%d\n", Add(1, 2));
    return 0;
}


8. 选择语句

前面我们看到的代码都是按照顺序执行的,也就是先执行第一条语句,然后是第二条、第三条…一直到最后一条语句,这称为顺序结构。
接下来我们介绍选择语句,比如你在生活中选择学习编程。你好好学习了可能会成为大牛,但是你不好好学习可能会有让你后悔的结果。在C语言中,使用if和else关键字对条件进行判断,所以从现在开始你要好好学习编程嘛?




//标准写法格式
#include <stdio.h>
int main()
{
    if('条件')
    {
    '符合条件执行的代码'
    }
    else
    {
    '不符合条件执行的代码'
    }
}

下面我们结合上面代码流程图来写一段代码:

#include <stdio.h>
int main()
{
	int input = 0;
	printf("你要好好学习编程吗?(选择1 or 0)\n");
	scanf("%d", &input);
	
	if (input == 1)
	{
		printf("成为大牛");  //选择1,成为大牛
	}
	else
	{
		printf("回家放牛"); //选择0,回家放牛
	}

	return 0;

}

让我们运行一下,可以看到光标在闪烁等待我们输入。我们选择1好好学习:

如果我们选择0来看看运行结果吧!


9. 循环语句

有些事早我们日常生活中必须一直做,比如学习如何学好编程,如何熟练敲代码,我们应该日复一日坚持下去!才会有结果!

///标准写法格式

while'条件'{
    '条件成立执行'
}

下面让我们写上一段代码,比如说买彩票,我一直不断重复买。每天我就只买一张,等我买了5000张的时候突然就中奖了!那还不是直接上任CEO,迎娶白富美!


#include <stdio.h>
   int main()
{
	int line = 0;
	while (line < 5000)
	{
		printf("%d:买彩票\n", line);
		line++;
	}
	if (line == 5000)
	{
		printf("中500w了,迎娶白富美");
	}

	return 0;
}

让我们运行起来:



我们可以看到,代码一直循环了5000次,最后一次达到了5000的条件它停了下来。


10. 函数

数学中的函数 f(x) = 2×x+5,在C语言中的函数也是一样的。

//计算两个数之间的和
#include <stdio.h>
int main()
{
    int num1 = 0;
    int num2 = 0;
    int sum = 0;
    printf("输入两个操作数:");
    scanf("%d%d", &num1, &num2);
    sum = num1 + num2;
    printf("sum = %d\n", sum);
    return 0;
}

下面让我们运用函数来写:

#include <stdio.h>
int Add(int x, int y)
{
   int z = x+y;
   return z;
}
int main()
{
    int num1 = 0;
    int num2 = 0;
    int sum = 0;
    printf("输入两个操作数:");
    scanf("%d%d", &num1, &num2);
    sum = Add(num1, num2);
    printf("sum = %d\n", sum);
    return 0;
}

需要注意的是:
函数的特点就是简化代码,代码复用。


11. 数组

要存储一串数字或者是1-10的数字,我们应该怎么存储?
C语言中给了数组的定义:一组相同类型元素的集合

11.1 数组的定义

int arr[10]={1,2,3,4,5,6,7,8,9,10};

11.2 数组的下标

C语言规定:数组的每个元素都有一个下标,下标是从0开始的。
数组可以通过下标来访问的。

int arr[10] = {0}; //如果数组有10个元素


11.3 数组的使用

int main()
{
    int a = 0;
    int b = 0;
    int c = 0;
    int d = 0;   //当我们要创建多个同类型变量的时候,就可以用数组来创建
    
    int arr[10] = {1,2,3,4,5,6,7,8,9,10}; // 把10个整型1到10存起来
    char ch[5] = {'a', 'b', 'c'}; // 不完全初始化,剩余的默认为0
    
    printf("%d\n", arr[0]); // 用下标就可以访问第一个元素了 打印出来是1
    
    // 打印整个数组
    int i = 0;
    while(i<10) 
    {
        printf("%d ", arr[i]);
        i++;
    }
    
    return 0;
}


12. 操作符

操作符里面的内容比较多,我们先基本了解一下!

12.1 算术操作符

+  -   *   /(除)  %(取余)

#include <stdio.h>
int main()
{
  int add = 1 + 1;
  int sub = 5 - 3;
  int mul = 3 * 3;
  float div = 7 / 2.0;    //只要有浮点数出现,执行的就是浮点数除法(加上.0),
                          //不想执行结果为为double类型可以在任意两个操作数后面加上f
                         //当执行精度比较高的浮点数计算可以用double类型 
  int mod = 1234 % 10;  //计算结果是1234/10的余数

  return 0;
}

12.2 移位操作符

>> (左移操作符)
<< (右移操作符)

  #include <stdio.h>
  int main()
  {   //移位操作符 移的是它二进制位
      int a=1;   //a的二进制位是 00000000 00000000 00000000 00000001
      int b=a<<1;//将a的二进制整体向前移动一位,左边丢弃,右边补零。
                 //我们得到的是  00000000 00000000 00000000 00000010
      printf("%d %d",a,b);          
  }

运行结果:

12.3 位操作符

& (按位与)
|(按位或)
^(按位异或)

12.3.1 按位与

#include <stdio.h>
int main()
{   //在C语言规定中,0为假,非0为真。
	int a = 3;  
	int b = 5;
	//&按位与 都为真,则返回真。只要出现假,就返回假。
	int c = a & b;    
	printf("%d", c);
	//00000000 00000000 00000000 00000011
	//00000000 00000000 00000000 00000101
	//00000000 00000000 00000000 00000001  打印结果为:1                            

	return 0;
}

12.3.2 按位或

#include <stdio.h>
int main()
{   
	int a = 3;  
	int b = 5;
	//|按位与 有真就为真,无真就为假。只要出现真,就为真.
	int c = a | b;    
	printf("%d", c);
	//00000000 00000000 00000000 00000011
	//00000000 00000000 00000000 00000101
	//00000000 00000000 00000000 00000111  打印结果为:7                          

	return 0;
}

12.3.3 按位异或

#include <stdio.h>
int main()
{
	int a = 3;
	int b = 5;
	
	int c = a^b;  // ^按位异或  相同为假,相异为真 
	printf("%d", c);
	//00000000 00000000 00000000 00000011
	//00000000 00000000 00000000 00000101
	//00000000 00000000 00000000 00000110  打印结果为:6                       

	return 0;
}

12.4 赋值操作符

= +=  -=  *=  /=  &=  ^=  |= >>= <<=

#include <stdio.h>
int main()
{
    int a = 10;
    a = 20;     // “=” 赋值 “==” 判断相等

    a = a + 10; 
    a += 10;    //两个式子是等价的

    a = a - 20;
    a -= 20;    //两个式子是等价的

    a = a & 2;
    a &= 2;    //这些操作叫做 复合赋值符
    
    a = a ^ 2;
    a ^= 2;   

    //.....
    return 0;
}

12.5 单目操作符

符号释义
!逻辑反操作
负值
+正值
&取地址
sizeof操作数的类型长度(以字节为单位)
~对一个数的二进制按位取反
前置、后置–
++前置、后置++
*间接访问操作符(解引用操作符)
(类型)强制类型转换

12.5.1 逻辑反操作


int main()
{
    //0表示假,非0就是真
    int a = 5;
    printf("%d\n", !a);
    int a =3
    int b= 0;  //b为假
    if(a)
    {
        //如果a为真,执行程序。
    }
    if(!b)    //此时b为假,我们用逻辑反操作符使它变成真
    {
        //如果b为假,执行程序。
    }
 
    while(a); //a不为假,做事
    while(!a); //a为假,做事
    return 0;
}

12.5.2按位取反

int main()
{
    int a = 0;
    printf("%d\n", ~a);
    // ~ 按(二进制)位取反 
    //把所有二进制位的数字,1变成0,0变成1
 
    //整数a
    // 0
    // 00000000000000000000000000000000(原码)
    // 11111111111111111111111111111111(补码)  ~a 
    //我们现在把补码转换成原码
    //11111111111111111111111111111111(补码)
    //11111111111111111111111111111110(补码-1,得到反码)
    //10000000000000000000000000000001(符号位不变,其他都变)
    //最后打印结果为-1
    
    // 数据在内存中存储的是补码
    // 一个整数的二进制表示有三种: 原码 反码 补码
    
    // 负数的计算  -1:
    // 10000000000000000000000000000001 (原码)
    // 11111111111111111111111111111110 (反码)
    // 11111111111111111111111111111111 (补码) -> 在内存中储存的形式
    
    //源码->反码:符号位不变其他位都变
    //反码->补码:反码+1
    // 正的整数: 原码、反码、补码相同
 
    return 0;
}

12.5.3 前置++

#include <stdio.h>
int main()
{
	int a = 1;
	int b = ++a; 
	//++a:是先++,再使用
	//这个时候先a+1=2,再使用把2赋值给b
	printf("a=%d b=%d", a, b);

	return 0;
}

运行结果:

12.5.3 后置++

#include <stdio.h>
int main()
{
	int a = 1;
	int b = a++; 
	//a++:先使用,再++
	//这个时候先把a的值赋给b,再a+1=2
	printf("a=%d b=%d", a, b);

	return 0;
}

运行结果:

12.5.4 强制类型转换

#include <stdio.h>
int main()
{
    int a = (int)3.14; //将3.14强制类型转换成int类型
    printf("%d\n", a);
 
    return 0;

12.6 关系操作符

符号释义
>大于
>=大于等于
<小于
<=小于等于
!=用于测试“不相等”
==用于测试“相等”

12.7 逻辑操作符

&&   (逻辑与)
||   (逻辑或)
逻辑与:两个为真都为真,在我们生活用语中是并且的意思。
逻辑或:一个为真即为真,在我们生活用语中是或者的意思。

上才艺:

12.7.1 逻辑与

#include <stdio.h>
int main()
{
    //0为假,非0为真
	int a = 3;      //a为真
	int b = 2;      //b为真
	int c = a && b; //a逻辑与b 

	printf("%d", c);  //输出结果为1,为真
	                  //两个操作数必须都为真,有一个为假那就都为假

	return 0;
}

12.7.2 逻辑或

#include <stdio.h>
int main()
{   //0为假,非0为真
	int a = 3;      //a为真
	int b = 0;      //b为假
	int c = a || b; //a逻辑或b 

	printf("%d", c);  //输出结果为1,为真
	                  //两个操作数中有一个是真,那就为真

	return 0;
}

12.8 条件操作符

exp1 ? exp2 : exp3
条件操作符就是三目操作符:

  • exp1成立,exp2计算,整个表达式的结果是:exp2的结果。
  • exp1不成立,exp2计算,整个表达式的结果是:exp3的结果。
#include <stdio.h>
int main()
{
    //exp1成立,exp2计算,整个表达式表达的是exp2的结果
    //exp1不成立,exp3计算,整个表达式表达的是exp3的结果
    int a = 0;
    int b = 3;
    int max = 0;
 
    /* 等价于
    if(a>b) {
        max = a;    
    } else {
        max = b;
    }
    */   //如果a大于b,表达式结果为a,如果不大于结果就为b
 
    max = a>b ? a : b;  
    printf("%d",max);
 
    return 0;
}

运行结果:

12.8 逗号表达式

exp1, exp2, exp3, …expN

#include <stdio.h>
int main()
{
    int a = 0;
    int b = 3;
    int c = 5;
            
    int d = (a=b+2, c=a-4, b=c+2);//用逗号隔开,取逗号后面最后表达式的结果。
            // a=5   c=5-4  b=1+2
    printf("%d\n", d); // 运行结果为3
    printf("%d %d %d\n", a, c, b);
    return 0;
}

运行结果:

我们可以看到,a,b,c这三个变量其实参与逗号表达式运算后,也改变了相应的值。

12.9 下标引用、函数调用和结构成员

[ ]  ( )  . ->

12.9.1 下标引用操作符

#include <stdio.h>
int main()
{
	int arr[5] = { 1,2,3,4,5 }; //创建一个数组

	printf("%d", arr[2]);  //从下标0开始,3的下标是arr[2],打印结果为3

	return 0;
}

12.9.2函数调用操作符

#include <stdio.h>
int main()
{
	printf("Hello World!"); 
    //printf("<格式化字符串>", <参量表>)是一个库函数。
    //调用函数时,函数名后面的()就是函数调用操作符。

	return 0;
}

  • 注:点操作符和箭头操作符我们稍后在结构体中再介绍。

13. 格式控制符

格式控制符说明
%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 类型的小数
%e、%le、%E、%lE以指数的形式输出 float、double 类型的小数。如果 e 小写,那么输出结果中的 e 也小写;如果 E 大写,那么输出结果中的 E 也大写。
%g、%lg、%G、%lG以十进制和指数中较短的形式输出 float、double 类型的小数,并且小数部分的最后不会添加多余的 0。如果 g 小写,那么当以指数形式输出时 e 也小写;如果 G 大写,那么当以指数形式输出时 E 也大写。
%s输出一个字符串

写几个常见的输出格式控制符:

#include <stdio.h>
int main()
{
	char ch = 'a';
	char arr[] = "abcdef";
	short num = 1;
	int a = 100;
	float f = 2.2;
	double Π = 3.141592;

	printf("%c\n", ch);  
	printf("%s\n", arr);
	printf("%d\n", num);
	printf("%d\n", a);
	printf("%f\n", f);
	printf("%lf\n", Π);

	return 0;
}

运行结果:


14. 常见关键字

关键字说明
auto声明自动变量,缺省时编译器一般默认为auto;最宽宏大量的关键字
int声明整型变量
double声明双精度变量
long声明长整型变量
char声明字符变量
float声明浮点型变量
short声明短整形变量
signed声明有符号类型变量
unsigned声明无符号类型变量
struct声明结构体变量;结构体所占内存大小是其成员所占内存之和
enum声明枚举类型变量
union声明联合数据类型变量
static声明静态变量;最名不符实的关键字
switch用于开关变量
case用于语句分支
default开关语句中的其他分支
break跳出当前循环;表示终止本层循环
continue结束当前循环,开始下一轮循环
register声明寄存器变量;最快的关键字
const声明只读变量,其修饰的只读变量必须在定义的同时初始化
volatile说明变量在出现执行中可被隐含地改变 ;最易变的关键字
typedef用以给数据类型取名
extern声明变量是在其他文件中声明;最会带帽子的关键字
return子程序返回语句,用来终止一个函数并返回后面跟着的值
void声明函数无返回值或者无参数,声明空类型指针
do循环语句的循环体
while循环语句的循环条件
for一种循环语句
if条件语句
else条件语句否定分支
goto无条件跳转语句
sizeof计算对象所占内存空间大小

要注意的是:

  • 关键字是C语言提供的,不能自己创建关键字
  • 关键字不能做变量名

14.1 关键字基本介绍

接下来让我们简单介绍几个关键字了解一下:


14.1.1 register关键字

我们要知道电脑cpu(中央处理器)在寄存器里面拿数据速度非常快,我们也可以把数据放在寄存器里面,这样以来可以提高工作效率。

register :声明寄存器变量

#include <stdio.h>
int main()
{
    
    register int n = 1000; //建议num的值存放在寄存器中
    //最终是不是放到寄存器中是编译器决定的
    // 其实现在编辑器已经足够人性化了
    // 你不主动加register,它也会帮你存到寄存器中。
    return 0;
}

14.1.2 typedef关键字

typedef:类型重定义

#include <stdio.h>
typedef unsigned int u_int; //我们给unsigned int取个名字叫u_int
int main()
{
    unsigned int num1 = 100;  //每次写个unsigned int很麻烦
    u_int num2 = 100;        //num2现在等价于num1
 
    return 0;
}

14.1.3 static关键字

最名不副实的关键字,没有之一!

static:声明静态变量
static关键字有三种用法

  • 修饰局部变量
  • 修饰全局变量
  • 修饰函数

接下来我们一个一个的学习!
1.修饰局部变量:

#include <stdio.h>
void test()
{
	 int a = 3;       //每次进来重新创建变量a
	 a++;             //每次++一下 test函数会进来十次       
	printf("%d ", a); //每次进来一次,打印一次,出函数体局部变量变量a销毁
}                    //从内存角度来讲a被放在栈区,我们可以称其为临时变量,进入作用域创建,出作用域销毁
int main()
{
	int i = 0;
	while (i < 10)   //i循环10次从0开始
	{
		test();     //调用10次test函数
		i++;        //每调用一下就++一下
	}

	return 0;
}

打印结果:

如果我们在test函数内部,变量a前面加一个static,打印结果又会发生什么改变呢?

#include <stdio.h>
void test()
{
	static int a = 3;  //我们在整型变量a在前面加上static来修饰局部变量
                       //第一次进去函数体创建的值,出了函数体局部变量a不销毁,保留了上次的值
                      //从内存角度来讲临时变量a被static修饰后被放在静态区。
                      //进入作用域它已经创建好了,出作用域了它也不销毁,所以  他的生命周期比较长。

	a++;
	printf("%d ", a);
}
int main()
{
	int i = 0;
	while (i < 10)
	{
		test();
		i++;
	}

	return 0;
}

运行结果:

结论:

  • 普通的局部变量是放在内存的栈区上,进入局部范围创建,出局部范围销毁
  • 当static修饰局部变量的时候,局部变量是在静态区开辟空间的,这时的局部变量出了作用域不销毁。下次进入作用域,使用的还是上次遗留下来的数据。实际上static修饰局部变量本质上是改变了局部变量的存储位置,从栈区到了静态区,使得变量的生命周期发生了变化,到程序结束,生命周期才结束。

我们简单来了解一下C语言内存分配:
内存分配为三种:栈区、堆区、静态区。

2.修饰全局变量
我们先看没有被static修饰过的全局变量,我们在同一个工程下创建了两个源文件,然后我们可以通过extern声明外部符号,可以将在add.c源文件下的year变量在20220609.c的源文件下打印出来,如图:

如果我们使用static修饰全局变量,结果又会怎么样呢?


结果如下:

这里我们可以看到编译器报错了,无法解析的外部符号。

结论:

  • 全局变量具有外部链接属性,如果全局变量被static修饰,这个外部链接属性就变成了内部链接属性了,也就是说这个全局变量只能在内部使用了,因为其他文件没办法通过链接找到这个全局变量了。

3.修饰函数
static修饰函数和修饰全局变量是非常相似的,我们直接看代码图片:

如果我们将函数用static修饰过后呢?

结论:

  • 函数是具有外部连接属性的,只要正确声明就可以使用,其他源文件就可以正常使用。
  • 但函数被static修饰过后,外部链接属性就变成了内部链接属性。这个时候函数只能在自己所在的.c文件内部使用,不能在外部使用了。

链接属性其实有三种:外部连接属性、内部链接属性、无链接属性。
外部链接属性指的是只要通过外部属性链接的互相能看到的,都叫做外部连接属性。内部链接属性是指被static修饰过的就是内部链接属性,无链接属性其实指的就是局部变量。
不过要注意的是,static修饰的全局变量和函数只是改变了它们的链接属性,并没有改变存储的区域,他们两个都是被放在静态区的,没有动。


15. #define定义常量和宏


我们要分清,define是预处理指令,不是关键字。

define的用法有两种:

  • define定义标识符常量
  • define定义宏

15.1 #define基本用法介绍

15.1.1define定义标识符常量

#include <stdio.h>

#define MAX 1000     //define定义一个常量,不用加等于号分号

int main()
{
    printf("%d\n", MAX);  //打印结果为1000

    int a = MAX;
    int arr[MAX] = { 0 };   //数组可以创建 说明define定义的MAX是常量
    
    return 0;
}

当然define不仅可以创建整型常量,其他常量类型也可以:

15.1.2 define定义宏

#include <stdio.h>

#define ADD(x,y) ((x)+(y))  //ADD是宏  (x+y)是宏的参数  ((x)+(y))是宏体 

#define MAX(x,y) ((x>y)?(x):(y)) //我们还可以用宏来比较大小
int main()
{
	int a = 10;
	int b = 20;
	
	int c = ADD(a, b);    //宏的调用 ,跟函数的调用非常相似
    int d = MAX(a, b);

	printf("ADD=%d\n",c);
    printf("MAX=%d\n",d);
	return 0;
}

运行结果:

  • 我们可以看到,宏的调用与函数非常相似,只不过在实现的过程中有所差异。宏的参数不需要类型,但函数的参数是需要类型的。宏没有函数体,在表达式后面加上()就是宏体,但函数需要函数体。
  • 宏的参数是没有类型的。
  • 宏的参数是替换进去的,不是传参。


16. 初探指针


我们只是初探一下指针的基本概念,之后更新了再详细说明。

16.1 内存

要了解指针,我们就需要先了解计算机的内存。内存就是我们买电脑的时候,所说的内存条它有8G/16G的还有更大的32g的,俗称运存,其实这个就叫做内存。我们可以通过Ctrl+Shift+ESC快捷键来观察,在任务管理器中的性能选项就能找到。

  • 内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。
  • 所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节
  • 为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址

内存是很大一块空间,有8g/16g,那我们怎么有效划分给每个程序使用的呢?


我们已经知道了计算机如何给程序划分空间的,那我们写的每个代码,也是需要内存来给我们分配空间的。比如变量是创建内存中的(在内存中分配空间的),每个内存单元都有地址,所以变量也是有地址的。

#include <stdio.h>
int main()
{
	int a = 10; //我们向内存申请了4Byte内存空间
    printf("%p",a);//打印地址,%p是以地址的形式打印
	return 0;
}

下面让我们调试起来看看内存编译器环境下如何查看并分配空间的。
按F10(逐过程)让代码调试起来,然后在编译器找到内存我们来观察一下
观察内存:调试—窗口—内存—内存1(选择任意1、2、3、4即可)

结论:

  • 当变量a创建好之后,占用4个字节。
  • 每个字节都有一个地址(地址编号)。
  • &a拿到的是第一个(起始位)字节的地址。
  • 编号就是内存单元的地址。
  • 我们把地址也叫作指针,所以编号=地址=指针。

16.2 指针介绍

我们可以看到地址的是0x开头的一串数值,0x开头代表着十六进制的数值,如果我们想把地址编号存起来应该怎么操作呢?

#include <stdio.h>
int main()
{
	int a = 10;    //我们向内存申请了4Byte内存空间

	 //pa = &a   pa创建出来是用来存放地址(指针)的

	int* pa = &a;  //现在pa就是一个指针变量 用来存放a的地址
	               //pa      
	
	return 0;
}

结论:

  • 需要储存地址,那就需要创建指针变量
  • 指针其实就是地址,地址就是内存单元的编号。
  • 把地址进行存储的时候,就可以放到一个变量中,这个变量就是指针变量
  • 口头语中说的指针,一般指的是指针变量。

16.3 解引用操作

#include <stdio.h>
int main()
{
    int a = 10;
    int* pa = &a; //pa创建出来是用来存放地址(指针)的
    *pa = 20;    //*是解引用操作符又称间接访问操作符
                 //*pa就是通过pa中存放的地址,找到pa指向的空间a
    return 0;
}

16.4 指针变量的大小

指针变量的大小是多少呢?

nt main()
{
    printf("%d\n", sizeof(char*));
    printf("%d\n", sizeof(short*));
    printf("%d\n", sizeof(int*));
    printf("%d\n", sizeof(long*));
    printf("%d\n", sizeof(long long*));
    printf("%d\n", sizeof(float*));
    printf("%d\n", sizeof(double*));
 
    return 0;
}
  • 指针变量的大小取决于地址的大小
  • 32位平台下地址是32个bit位(即4个字节)
  • 64位平台下地址是64个bit位(即8个字节)
  • 所以指针大小在32位平台是4个字节,64位平台是8个字节。


17. 结构体

结构体是C语言中特别重要的知识点,结构体使得C语言有能力描述复杂类型。
比如描述学生,学生包含: 名字+年龄+性别+学号 这几项信息。
这里只能使用结构体来描述了。


#include <stdio.h>
// 创建一个学生的类型
struct Stu {
    char name[20]; // 姓名
    char sex[5];//性别
    int age; // 年龄
    float score; // 分数
};  //加分号
int main()
{
    struct Stu s = {"张三","男", 20 ,88.5f};   //创建结构体变量并进行初始化

    // .操作符使用格式:结构体变量.结构体成员
    // .为结构成员访问操作符 
    //打印结构体信息
    printf("name = %s sex=%s age = %d score = %f\n", s.name,s.sex,s.age,s.score);


    return 0;
}

打个比方,结构体类似于我们盖房子的图纸。我们创建结构体变量的类型就相当于我们盖的房子。我们看下打印结果:

我们能不能用地址进行传参,打印结构体呢?

#include <stdio.h>
// 创建一个学生的类型
struct Stu {
    char name[20]; // 姓名
    char sex[5];//性别
    int age; // 年龄
    float score; // 分数
};  //加分号
void Print(struct Stu *ps)
{   //可以用点操作解引用进行打印,但是不推荐
    printf("%s %s %d %f\n",(*ps).name, (*ps).sex,(*ps).age,(*ps).score);

    //推荐
    //->箭头操作符
    //使用格式:结构体指针->结构体成员
    //在内存里面ps其实指向的就是结构体变量s里的成员
    printf("%s %s %d %f\n", ps->name, ps->sex, ps->age, ps->score);
}
int main()
{
    //打印结构体信息
    struct Stu s = { "张三","男", 20 ,88.5f };   //创建结构体变量并进行初始化


    //我们也可以使用scanf输入数据
    //需要注意的是数组变量名就是地址,其他类型在输入时需要取地址
    scanf("%s%s%d%f", s.name, s.sex, &(s.age), &(s.score));

    Print(&s);  //我们把s的地址传参过去

    //我们也可以使用scanf输入数据
    scanf("%s%s%d%f", s.name,s.sex,s.age,s.score);

    return 0;
}


————本章完————

更多推荐

【零基础】入门C语言这一篇就够了