C语言基础(上)

常见的C语言编译器

Clang、GCC、WIN-TC、SUBLIME、MSVC、Turbo C
推荐使用 GCC 和 MSVC(Microsoft Visual Studio)

MSVC(Microsoft Visual Studio) 下载安装

Visual Studio 下载地址
Visual Studio 对学生、个人是免费的,对企业是收费的。
下载过程中,注意勾选下面一个,这样才能合理的开始写C。
在这里插入图片描述

安装完成后,运行c语言程序,会看到命令行一闪而过的情况,此时:
右键文件 —> 属性 —> 链接器 —> 系统 —> 子系统
然后选中控制台即可

第一个C语言程序

  1. 创建Project
  2. 创建文件
    C语言当中,文件后缀如下:
    .c – 源文件
    .h – 头文件
  3. 写代码:
// 第一段代码
// standard input output
#include // int是整型,main前面的int表示main函数返回一个整形值
int main() // 主函数-程序的入口,主函数有且仅有一个
{// printf是打印函数,是C语言的库函数,所需需要导入stdio.hprintf("hahah\n");return 0;
}// void 是返回值为空的函数类型
void main()
{
}
// 第二段代码// sizeof 取大小printf("%d\n", sizeof(char));		// 1 字节printf("%d\n", sizeof(short));		// 2 字节printf("%d\n", sizeof(int));		// 4 字节printf("%d\n", sizeof(long));		// 4/8 字节printf("%d\n", sizeof(long long));	// 8 字节printf("%d\n", sizeof(float));		// 4 字节printf("%d\n", sizeof(double));		// 8 字节
// 第三段代码
int main()
{// 计算两数之和int num1 = 0;int num2 = 0;int sum = 0;printf("11");// 输入函数-scanf,意思是代码执行到该地方需要输入两个值scanf_s("%d %d", &num1, &num2); // 取地址符号 &// C语言规定,声明变量要放在代码块的最前面sum = num1 + num2;printf("sum = %d\n", sum);return 0;
}

VS的一个报错

在这里插入图片描述
在使用 scanf 函数时,会报上图的这样一个错误,解决方案如下:

scanf 是C语言提供的。
scanf_s 不是标准C语言提供的,而是VS编译器提供的。

尽量不要使用scanf_s,不然只有VS才能识别,这样不具有跨平台性。
报错信息里面 _CRT_SECURE_NO_WARNINGS 拷贝出来,然后在源文件第一行加上:

#define _CRT_SECURE_NO_WARNINGS 1也可以将这句话添加到默认生成当中,先找到
D:\Visual Studio\Common7\IDE\VC\VCProjectItems\newc++file.cpp
然后将这句话粘贴进去,然后保存即可,保存不了,就用notepad++来搞。

即可解决scanf报错问题。
除了scanf,还有其他库函数 strcpy、strlen、strcat、strcpy_s 等函数也会出现这个警告。

C语言数据类型

char字符数据类型
short短整型
int整型
long长整型
long long更长的整形
float单精度浮点数
double双精度浮点数

格式化输出

%d整型
%c字符
%f浮点型
%lf双精度浮点数
%p以地址的形式打印
%x十六进制
%u打印十进制的无符号数字
%o

C语言变量作用域、生命周期

C语言中,全局变量的作用域是整个工程,在代码中通过 extern 来声明外部定义。

局部变量生命周期: 左括号开始,右括号结束。
全局变量生命周期:是整个程序的生命周期。

常量

分类示例
字面常量int num = 4; (可变)
const修饰的常变量const int num = 4; (不可变)
#define定义的标识符常量#define MAX 10(不可变)
枚举常量 --enumenum Sex{ MALE,FEMALE,SECRET};(不可变)

常变量: 它本质上还是一个变量,只是拥有了常属性。

// const 常变量介绍
#include 
#include #define A 10int main()
{	const int a = 1;// 这句可以正常执行int arr[A] = { 0 };// 由于 a 是一个常变量,所以执行这句时会报错int arr1[a] = { 0 };
}
// 枚举常量示例
#include 
enum Color
{RED,YELLOW,BLUE
};int main()
{enum Color color = BLUE;// Color 不能改,但是color可以改color = YELLOW;return;
}

字符串 + 转义字符 + 注释

字符串

"hello world!"

这种双引号引起来的就叫做字符串,单引号引起来的叫字符,也就是char。

#include 
#include 		// strlen 函数是在这个库里的
int main()
{char arr1[] = "abc";			// 把字符串放进了数组,可以单放char arr2[] = {'a', 'b', 'c'};	// 把三个字符放进来数组printf("%s\n", arr1);	// arr1中有四个元素,a,b,c,0,打印结果:abcprintf("%s\n", arr2); 	// arr2中有三个元素,a,b,c,打印结果:abc烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫贴-戽?printf("%d\n", strlen(arr1));	// 结果:3printf("%d\n", strlen(arr2));	// 结果:是个随机值,直到遇到 '\0' 才会停止return 0;
}

此时,如果给 arr2 手动加一个 0,结果就会一样,那是因为:

字符中,末尾会有一个 '\0'  的转义字符,它是字符串的结束标志,不算字符串内容。

转义字符

转义字符串说明
?在书写连续多个问号时使用,防止他们被解析成三字母词
用于表示字符常量’
"用于表示一个二祖父穿内部的双引号
\\用于表示一个反斜杠,防止它被解释为一个转义序列符
\a警告字符,蜂鸣
\b退格符
\f进纸符
\n换行
\r回车
\t水平制表符
\v垂直制表符
\dddddd表示1~3个八进制数字
\xdd\xdd表示2个十六进制数字

注:最后两个在打印时,表示八进制数对应的十进制值,在ASCII码表中对应的字符。

if 语句、循环简介

if 语句示例

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
int main()
{int input = 0;scanf("%d", &input);if (input == 1)printf("ok\n");elseprintf("Noo");return 0;
}

循环实现方式语法

  • while 语句
#include 
#include int main()
{int input = 0;while (input < 2000){input++;printf("%d \n", input);}printf("执行结束");return 0;
}
  • for 语句
  • do … while 语句

函数简介

  • 库函数
  • 自定义函数
#include 
#include int Add(int x, int y)
{int z = x + y;return z;
}int main()
{int num1 = 5;int num2 = 8;int sum = Add(num1, num2);printf("%d \n", sum);
}

数组

一组相同类型的元素的集合
示例: int arr[10] = {1,2,3,4,5,6,7,8,9,10}; //定义一个整型数组,最多放10个元素

使用

#include 
#include int main()
{char ch[20];float fl[5];int arr[10] = {1,2,3,4,5,6,7,8,9,10};printf("%d \n", arr[4]);
}

操作符

  • 算术操作符

      +     -     *     /     %加    减    乘     除    取余(取模)
    
  • 移位操作符

      >>      <<右移    左移		1. 左移操作符,左边丢弃,右边补0.2. 算术右移: 右边丢弃,左边补原符号位。3. 逻辑右移:右边丢弃,左边补0。4. C编辑器以及通常见到的都是算术移位。
    
  • 位操作符

        &        |         ^按位与    按位或    按位异或		补充:按二进制位操作,他们的操作数必须是整数。与操作:0-假,1-真,11是1,其他全0或操作:有1就是1,不管是1个还是两个异或操作:对应二进制位相同为0,相异则为1
    
  • 赋值操作符

      =   +=   -=   *=   /=   &=   ^=   |=   >>=   <<=
    
  • 单目操作符

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

    补充:前置++,先使用,再++。后置++,先++,再使用。

  • 条件操作符(三目操作符)

      exp1 ? exp2 : exp3
    

    说明:
    如果 exp1 为 真,则执行exp2,exp2的结果是整个表达式的结果。
    如果 exp1 为 假,则执行exp3,exp3的结果是整个表达式的结果。

// 示例:
#include 
#include int main()
{int a = 10;int b = 20;int max = 0;max = (a > b ? a : b);printf("%d \n", max);	// 结果:20
}
  • 逗号表达式

      exp1, exp2, exp3, ...expN
    

    说明:
    逗号表达式是一种运算符,用逗号将多个表达式连接起来。它的特点是从左到右依次执行这些表达式,并返回最后一个表达式的值作为整个逗号表达式的结果。

#include 
#include int main()
{int a = 1;int b = 2;int c = (a > b, a = b + 10, a, b = a + 1);printf("%d", c);		// result:13
}

逗号表达式的使用场景有以下几个:

// 1. 在函数调用或函数返回语句中,可以使用逗号表达式将多个表达式放在同一个位置
int a = 1, b = 2, c = 3;
int result = foo(a, b, c); // 函数调用中使用逗号表达式int max = (a > b ? a : b), min = (a < b ? a : b); // 函数返回语句中使用逗号表达式// 2. 在循环语句中,逗号表达式可以用于同时执行多个操作
for (int i = 0, j = n; i < j; i++, j--) {// 循环体代码
}//3. 在赋值语句中,逗号表达式可以用于顺序执行多个赋值操作,并将最后一个赋值的值作为整个表达式的结果
int a, b, c;
a = 1, b = 2, c = 3; // 多个赋值操作连写
  • 下标引用、函数调用和结构成员

       []		()		.		->下标      括号     点	    箭头
    

    示例:
    下标:arr[4]
    括号:Add(a, b)
    点:用在结构体获取成员
    箭头:用在结构体获取成员

  • 常见关键字

      auto    bread    case    char    const	 continue    default    dodouble    else    enum   extern  float     for   goto  if   int  longregister  return  short  signed  sizeof   static  struct  switch  typedefunion  unsigned   void  volatile  while 
    

    关键字解释

    关键字解释
    auto本来局部变量在前面有auto,因为都有,就被省略了,例如:auto int num = 2;
    break跳出循环
    caseswich case 语句中的
    char字符类型
    const常变量
    continue继续
    default默认,switch case 语句中的
    dodo while 循环
    double双精度浮点类型
    elseif 语句中的
    enum枚举常量
    extern引入外部符号
    float单精度浮点数
    for循环语句
    gotogoto 语句
    if判断语句
    int整型
    long长整型
    register寄存器关键字,寄存器是CPU访问速度是最快的,所以可以定义寄存器变量,例如register int a = 10; 这个只是建议把a定义成寄存器,具体有没有生效,取决于编译器
    return返回
    short短整型
    signed有符号数,平常定义的 int,其实就是 signed int
    sizeof计算大小,按照字节大小来计算
    static静态、修饰局部变量,使局部变量生命周期延长,也可以修饰全局变量(函数),改变它的作用域。修饰局部变量时:static int a = 1;此时,static使a这一变量不会因为进入作用域而生成,不因离开作用域而销毁,而是一直存在到程序运行结束,所以如何++的话,它会累加。修饰全局变量时:会让静态的全局变量只在自己所在的源文件内部使用,出了源文件就用不了了,修饰函数时,会改变函数的链接属性,让外部链接属性–>内部链接属性
    struct结构体关键字
    switchswitch case 语句
    typedef类型定义(类型重定义)
    union联合体(共用体)
    unsigned无符号数,例如:unsigned int num = 0;
    void无、空
    volatile体现C语言段位的一个关键字
    while循环语句
#include 
#include int main()
{	// typedef 介绍// 这里的意思是,给unsigned int重新起了一个名字叫u_int,也就是别名typedef unsigned int u_int;unsigned int a = 10;u_int num2 = 10;
}

#define 定义常量和宏

define 定义的常量#define MAX 1000
define 定义宏--带参数#define ADD(x,y) ((x) + (y))
#include 
#include // 函数的实现
Max(int x, int y)
{if (x > y)return x;elsereturn y;
}// 宏的实现
#define MAX(X, Y) (X > Y ? X : Y)int main()
{	int a = 10;int b = 20;// 通过函数的方式计算int max = Max(a, b);printf("max = %d\n", max);// 宏的方式max = MAX(a, b);printf("max = %d\n", max);
}

指针简介

了解指针,先了解内存地址,如何产生地址:计算机分为32位和64位,这里的位指的是32根或者64根地址线/数据线,一但通电就有高低电平。
代表0和1,32根电信号上产生的可能性,就有2的32次方个可能性,然后就会产生这么多编号,每一个
编号对应到内存块上,就成了内存块的地址。在设计内存的时候,每个内存的小空间就是1个字节。当
写出 int a = 1; 时,就申请了4个字节。指针指向的是内存单元。如果局部变量不初始化,局部变量的指针就会被初始化为随机值(这个很危险),叫做野指针。野指针:1. 指针没有初始化;2. 指针越界(明明有32个,但是指到了45个);3. 给指针赋值为NULL,让指针空置,可以有效避免空指针;4. 指针使用之前,监测指针的有效性。
  • 取地址、指针变量
#include 
#include int main()
{	int a = 10;// 取地址--&printf("%p \n", &a);	// 结果:0000009A39B8F7C4,这是一个16进制return 0;
}
#include 
#include int main()
{	int a = 10;// 存放地址的变量----指针变量,例子中是一个整型指针变量int* p = &a;// p前面的符号----解应用操作符,对p进行解引用操作,找到它所指向的对象a的地址*p = 20;printf("%d \n", a);	// 结果:20return 0;
}
  • 指针变量的大小

    在32位的平台上,一个指针变量的大小,需要4个字节。
    在64位的平台上,一个指针变量的大小,需要8个字节。

在VS上,可以点击
本地Windows调试器下拉框–>配置启动项目–>配置属性–>平台
来修改当前平台的位数。

  • 指针 ± 整数
#include 
#include int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);int* p = arr;for (i = 0; i < sz; i++){printf("%d \n", *p);p++;}return 0;
}
  • 指针 - 指针

      地址减地址的结果是:中间的元素个数,大地址减小地址,需要用同类型来减。
    
#include 
#include int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%d", &arr[9] - &arr[0]);	//result:9return 0;
}
  • 指针的关系运算(比较)

      允许指向数组元素的指针于指向数组最后一个元素后面的哪个内存位置的指针比较,但是不允许与指向第一个元素之前的哪个内存位置的指针进行比较。
    
#include 
#include int main()
{float values[5];float* vp;// 指针比较大小for (vp = &values[5]; vp > &values[0];){*--vp = 0;}
}
  • 指针与数组

      1. &arr -- &数组名不是首元素的地址,数组名表示整个数组,取出的是整个数组的地址。2. sizeof(arr) -- 计算整额数组的大小
    
#include 
#include int main()
{int arr[10] = { 0 };int* p = arr;int i = 0;for (i = 0;i < 10;i++){*(p + i) = i;printf("%d, %d\n", p + i, *(p + i));}for (i = 0; i < 10; i++){printf("%d ", *(p + i));}return 0;
}
  • 二级指针

      指针变量也是变量,是变量就会有地址,那指针变量的地址存放在哪里,这就是二级指针。
    
#include 
#include int main()
{int a = 10;int* pa = &a;int** ppa = &pa;		// ppa 就是二级指针int** * pppa = &ppa;		// pppa就是三级指针// 使用二级指针printf("%d", **ppa);return 0;
}
  • 指针数组

      存放指针的数组
    
#include 
#include int main()
{int a = 10;int b = 20;int c = 30;int* pa = &a;int* pb = &b;int* pc = &c;int* arr[3] = {&a, &b, &c};	//它就是一个指针数组int i = 0;for (i = 0; i < 3; i++){printf("%d \n", * (arr[i]));}return 0;
}

结构体

	结构体是C语言中特别重要的知识点,它使C语言有能力描述复杂类型。比如描述学生,学生包含:姓名、年龄、性别、学号等。语法:struct tg{member-list;}variable-list;下面是三个简单的示例:
// 写法一
#include 
#include struct Stu
{char name[20];short age;char tele[12];char sex[5];
}s1, s2, s3;	// s1、s2、s3 是三个全局的结构体变量,因为需要尽量少的创建全局表量,所以不建议写int main()
{struct Stu s;return 0;
}
// 写法二
#include 
#include typedef struct Stu
{char name[20];short age;char tele[12];char sex[5];
}Stu;		// 重新起了名字叫Stuint main()
{struct Stu s1;	// 局部变量Stu s2;return 0;
}
#include 
#include // 创建一个结构体类型
struct Book
{char name[20];short price;
};int main()
{	// 利用结构体类型创建一个该类型的结构体变量struct Book b1 = { "C语言程序设计" , 55};printf("%s \n", b1.name);printf("%d \n", b1.price);b1.price = 18;printf("%d \n", b1.price);// 如果改name的值,会发现name是一个数组,直接拷贝会报错// b1.name = "Python";	这么写会报错,正确写法如下:strcpy(b1.name, "Python");	// strcpy--字符串拷贝--是中的函数return 0;
}
#include 
#include // 创建一个结构体类型
struct Book
{char name[20];short price;
};int main()
{	struct Book b1 = { "C语言程序设计" , 55 };struct Book* pb = &b1;// 利用pb来打印出书名和价格(点操作符)printf("%s \n", (*pb).name);printf("%d \n", (*pb).price);// 利用pb指向来打印(箭头操作符)printf("%s \n", pb->name);printf("%d \n", pb->price);(*pb).price = 18;printf("%d \n", (*pb).price);return 0;}
  • 结构体成员

      结构体成员可以是不同类型,包括标量(变量)、数组、指针、其他结构体
    
  • 结构体的定义和初始化

#include 
#include typedef struct Stu
{char name[20];short age;char tele[12];char sex[5];
}Stu;int main()
{struct Stu s1 = {"张三", 20, "123456", "男"};Stu s2 = { "李四", 30, "456", "女" };return 0;
}
  • 结构体成员的访问

      结构体变量访问成员,结构变量的成员是通过 点 操作符(.)访问的。点操作符接受两个操作数,示例如下:
    
#include 
#include struct S
{int a;char c;char arr[20];double d;
};struct T
{char ch[10];struct S s;char* pc;
};int main() 
{char arr[] = "hello bit\n";struct T t = { "hehe", {100, 'w', "hello world", 3.14}, NULL};	// 结构体中放结构体printf("%s\n", t.ch);printf("%s\n", t.s.arr);printf("%lf\n", t.s.d);printf("%s\n", t.pc);return 0;
}
  • 结构体传参

      函数传参的时候,是需要压栈的,如果传递一个结构体对象时,结构体过大,会导致压栈的系统开销变大,性能下降。
    
#include 
#include typedef struct S
{char name[20];short age;char tele[12];char sex[5];
}Stu;void Print1(Stu s)
{printf("name:%s\n", s.name);printf("age:%d\n", s.age);printf("tele:%s\n", s.tele);printf("sex:%s\n", s.sex);
}void Print2(Stu* ps)
{printf("name:%s\n", ps->name);printf("age:%d\n", ps->age);printf("tele:%s\n", ps->tele);printf("sex:%s\n", ps->sex);
}int main() 
{Stu s = { "张三", 40, "123456789", "男" };// 打印结构体数据,Print2更好一点,因为Print1需要拷贝出来一份数据,占空间。Print1(s);Print2(&s);return 0;
}

分支、循环语句

分支语句

  • if

      语法一:if (表达式)执行;语法二:if (表达式)执行;else执行;语法三:if (表达式)执行;else if (表达式)执行;......else执行;
    
#include 
#include int main()
{	int age = 55;if (age < 18)printf("未成年");else if (age >= 18 && age < 28)printf("青年");else if (age >= 28 && age < 48)printf("中年");else if (age >= 48 && age < 80)printf("老年");elseprintf("牛逼");return 0;
}
注意事项
如果在if为真时,需要执行多条语句,那么需要加 {}!!! 在C语言中,由于没有缩进要求,所以 if 会和最近的 else 匹配 !!!可以通过 { } 也可以把 if 和 else 隔开
#include 
#include int main()
{	int age = 55;if (age < 18)printf("未成年");else if (age >= 48 && age < 80){printf("老年\n");printf("过寿");}elseprintf("牛");// { } 把 if 和 else 隔开if (age == 4){printf("aaa");	// 这个hello会被打印if (age == 55)printf("bbb");	// 这个hello会被打印}elseprintf("ccc");return 0;
}
  • switch

      是一种分支语句,常用语多分支,语法如下:switch(整型表达式){语句项;}语句项就是一些 case 语句,如下:case 整型常量表达式:语句;注意:是 整型 常量 表达式。用 break 来中止 switch 中 case 的执行。如果所有的 case 都不能满足需求,可以用 default 来解决,一般用来处理报错信息。switch 是允许嵌套使用的。
    
#define _CRT_SECURE_NO_WARNINGS 1#include 
#include int main()
{	int day = 0;scanf("%d", &day);switch (day){case 1:printf("星期一\n");break;case 2:printf("星期二\n");break;case 3:printf("星期三\n");break;case 4:printf("星期四\n");break;case 5:printf("星期五\n");break;case 6:printf("星期六\n");break;case 7:printf("星期天\n");break;}return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include int main()
{	int day = 0;scanf("%d", &day);switch (day){case 1:case 2:case 3:case 4:case 5:printf("工作日\n");break;case 6:case 7:printf("休息日\n");break;default:printf("输入错误\n")}return 0;
}

循环语句

  • while

      语法:while (表达式)循环语句
    
#include 
#include int main()
{	int i = 1;while (i<=10){if (i == 5)break;printf("%d \n", i);i++;}return 0;
}

EOF 代表 ctrl + z,他是文件结束标志,值为-1

#include 
#include int main()
{	/*int ch = getchar();	// 接收键盘输入的字符putchar(ch);		// 将字符输出到屏幕printf("%c\n", ch);*/int ch = 0;while (ch = getchar() != EOF)	// EOF 代表 ctrl + z,他是文件结束标志,值为-1{putchar(ch);}return 0;
}

getchar() 与 putchar() 练习

#include 
#include int main()
{	int ret = 0;int ch = 0;char password[20] = { 0 };scanf("%s", password);		// 在这里获取了密码while ((ch = getchar() != '\n'));					// 这里还有一个\n,需要获取{;}printf("请确认(Y/N):>");ret = getchar();if (ret == 'Y'){printf("确认成功\n");}else{printf("放弃\n");}return 0;
}
  • for

       语法:for (表达式1; 表达式2; 表达式3)循环语句;表达式1:初始化部分,用于初始化循环变量。表达式2:条件判断部分,用于判断程序合适中止。表达式3:调整部分,用于循环条件的调整。
    
#include 
#include int main()
{	int i = 0;for (i = 1; i <= 10; i++){if (i == 3)continue;if (i == 9)break;printf("%d \n", i);}return 0;
}

for 循环变种

  1. for 循环的初始化、调整、判断都可以省略
  2. for 循环的判断部分如果被省略、那判断条件就是:恒为真,也就是会死循环
#include 
#include int main()
{	for (;;){printf("hehe\n");}return 0;
}

其他补充:
在下面这个 for 循环中,当判断条件 j=0 时,由于0为假,所以不会进入到这个循环当中去,当 j = 其他值时,为真,所以会进入死循环。

#include 
#include int main()
{	int i = 0;int j = 0;for (i=0, j=0; j = 0;j++){printf("hehe\n");}return 0;
}
  • do while

      语法:do循环语句;while (表达式);
    
#include 
#include // 这段代码,当contine时,会死循环,break时,会跳出
int main()
{	int i = 1;do{if (i==5)continue;// if (i==8 )// 	break;printf("%d \n", i);i++;} while (i <= 10);return 0;
}

函数

  • 函数是什么

在计算机科学中,子程序,是一个大型程序中的某部分代码,由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。
一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏,这些代码通常被集成为软件库。

  • 库函数

      频繁使用的某个函数(例如:printf、strcpy、pow),详细信息登陆下面网站去看
    

    库函数学习网站

      C语言常用的库函数有:1. IO函数2. 字符串操作函数3. 字符操作函数4. 内存操作函数5. 时间/日期函数6. 数学函数7. 其他库函数
    
  • 自定义函数

      自己定义的函数,有返回值类型和函数参数。示例ret_type fun_name(para1, *){statement; // 语句项}
    
#include 
#include int Add(int x, int y)
{int z = x + y;return z;
}int main()
{	int i = 1;int j = 7;int k = Add(i, j);printf("%d \n", k);return 0;
}
  • 函数参数

      1. 实际参数真实传递给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数。无论实参是何种类型的量,在进行函数调用时,他们都必须有确定的值,以便把这些值传送给形参。2. 形式参数形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了,因此形式参数只在函数中有效,它只是一个临时copy的值,并不会影响实参。
    
  • 函数调用

      1. 传值调用函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。2. 传址调用传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
    
  • 函数的嵌套调用和链式访问

      1. 嵌套调用简单的来说,就是 A 调用 B,B 调用 C,C 调用 D2. 链式调用就是把一个函数的返回值作为另一个函数的参数,示例如下:
    
#include 
#include int main()
{return strlen("abc");
}
#include 
#include int main()
{printf("%d", printf("%d", printf("%d", 431)));	// 结果4321// 因为printf的返回值是字符长度
}
  • 函数的声明和定义

      函数的声明:1. 告诉编译器有一个函数叫什么、参数是什么、返回类型是什么。具体存不存在,无关紧要。2. 函数的声明一般出现在函数的使用之前。满足先声明、后使用。3. 函数的声明一般要放在头文件。注:简单来说,就是如果要写一个加法功能,先创建add.h 头文件add.c 源文件在头文件中声明函数#ifndef __ADD_H__	// 防止头文件的多次包含而引起的重复定义错误# define __ADD_J__int add(int x, int y);#endif在源文件中写函数具体功能int add(int x, int y);.....用的时候导入#include "add.h"注:#ifndef __ADD_H__	 防止头文件的多次包含而引起的重复定义错误(if not def),这个名字可以随便起,例如 #ifndef AAA,一般为了遵循C语言编程规范,用前后双下划线来进行标识,然后中间一般是函数名的全部大写加上_H。函数的定义:函数的定义是指函数的具体实现,交代函数的功能实现。
    
  • 函数递归

      一个函数自己去调用自己就叫递归,递归的两个必要条件:1. 存在限制条件,当满足这个条件,递归便不再继续,如果无线循环,会栈溢出。因为程序每次执行的时候会申请区域,有栈区、堆区、静态区:栈区:局部变量函数形参,函数调用堆区:同台开辟的内存,malloc、calloc静态区:全局变量static修饰的变量3. 每次递归调用之后越来越接近这个限制条件。
    
#include 
#include int main()
{printf("hehe\n");main();return 0;
}

main每次执行,都会上栈区申请一块空间,当栈空间被耗干,就会出现栈溢出报错。

#include 
#include void print(int n)
{if (n > 9){print(n / 10);}printf("%d ", n % 10);
}int main()
{unsigned int num = 0;scanf("%d", &num);print(num);return 0;
}

数组

  • 一维数组的创建和初始化

      数组是一组相同类型元素的集合,数组的创建方式:type_t    arr_name	[constan_n]元素类型	名称			常量表达式
    
#include 
#include int main()
{int arr[10];char arr2[5];float arr3[10];double arr4[10];return 0;
}
  • 一维数组的使用

      对于数组而言,使用 [] 操作符即可访问数组。
    
#include 
#include int main()
{int arr[10] = {1,2,3};int i = 0;while (i <= 9){printf("%d\n", arr[i]);i++;}return 0;
}
  • 一维数组在内存中的存储

      数组在内存中都是连续存放的,以下程序为例,打印结果如下图:
    
#include 
#include int main()
{int arr[10] = {1,2,3};int i = 0;while (i <= 9){printf("%d\n", &arr[i]);i++;}return 0;
}

在这里插入图片描述

  • 二维数组的创建、初始化、使用
#include 
#include int main()
{int arr[2][3] = {1,2,3,4};				// 表示数组2行3列 ,多出的第一行第四列会转移到第二行第一列int arr1[3][3] = { {1,2,3},{1,2},{1,2,3} };		// 赋值当中的每个数组代表一个维度int arr2[][3] = { {1,2,3},{1,2},{1,2,3} };		// 行可以省略,列不能省略return 0;
}
  • 二维数组在内存中存储
#include 
#include int main()
{int arr[3][4] = { {1,2,3 }, { 4,5 }};int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 4; j++)printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);}return 0;
}

在这里插入图片描述
在这里插入图片描述

  • 数组作为函数参数

      以冒泡排序为例(相邻元素比较大小,假设有10个元素,那么就需要9趟)
    
#include 
#include void bubble_sort(int arr[], int sz)
{int i = 0;for (i = 0; i < sz - 1;i++){// 每一趟排序的内容int j = 0;for (j = 0; j < sz-1-i; j++){if (arr[j] > arr[j + 1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}}int main()
{int arr[] = {9,8,7,6,5,4,3,2,1,0};	// 对arr进行排序,要求升序排列int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz);					// 冒泡排序函数// 打印结果for (i = 0; i < sz;i++){printf("%d ", arr[i]);}return 0;
}
  • 数组名是什么

      数组名是首元素地址,但是有两个例外:1. seizeof(数据名) -- 数组名表示整个数组,sizeof计算整个数组的大小,单位是字节2. &数组名 -- 数组名代表整个数组,&数组名 取出的是整个数组的地址
    
#include 
#include int main()
{int arr[10] = {1,2,3,4,5};printf("%p \n", arr);printf("%p \n", &arr[0]);printf("%d \n", *arr);
}

在这里插入图片描述

常用技巧

  1. 交换两个整型变量

     1. 进入企业:会采用第三个变量的方法,代码可读性搞,执行效率高;2. 异或的操作:可读性差,执行效率低于其他方法。注:用异或是因为整型是4个字节共32位,除去符号位,也就是说最大表示到 2**31 = 2147483648,超过这个就会溢出,就无法进行正常的交换,此时就需要用到异或。
    
#include 
#include int main()
{int a = 3;int b = 5;// 方法一:int temp = 0;printf("交换前:%d, %d\n", a, b);temp = a;a = b;b = temp;printf("交换后:%d, %d\n", a, b);// 方法二:printf("交换前:%d, %d\n", a, b);a = a ^ b;b = a ^ b;a = a ^ b;printf("交换后:%d, %d\n", a, b);
}
  1. const修饰指针(面试常见)!!!

     const 放在指针变量*号左边时,修饰的是 *p,不能通过p来改变*p的值。const 放在指针变量*号的右边,修饰的是指针变量本身,所以p就改不了。
    
#include 
#include 
#include int main() 
{const int num = 10;const int* p = &num;		// const 放在指针变量的*号左边时,修饰的是 *p,不能通过p来改变*p的值*p = 20;		// 这个是错误的printf("%d \n", num);return 0;
}
#include 
#include 
#include int main() 
{const int num = 10;int* const p = &num;*p = 20;p = &n;		// 这句是错的,执行会报错printf("%d \n", num);return 0;
}

数据类型的介绍

1. 内置类型char、short、int、long、float、double
2. 自定义类型 (构造类型)数组类型、结构体类型、枚举类型(enum)、联合类型(union)
3. 指针类型大小统一
4. 空类型void,通常应用于函数的返回类型、函数的参数、指针类型

整型在内存中的存储:原码、反码、补码

在这里插入图片描述

大小端字节序介绍及判断

什么是大端小端:大端(储存)模式:是指数据的低位,保存在内存的高地址中,而数据的高位,保存在内存的低地址中;小端(储存)模式:是指数据的低位,保存在内存的低地址中,而数据的高位,保存在内存的高地址中。
为什么要有大小端:因为存的时候可以随便存,但是要用的时候还要再拿出来,不规定格式拿出来就乱了。现在的电脑,几乎都是按照大小端来存储的。在计算机系统当中,以字节为单位,一个字节8bit,C语言中除了8bit的char类型外,还有16bit的short类型,32bit的long型(看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位处理器,由于寄存器宽度大于一个字节,那么必然存在着多个字节的安排问题,因此就导致了大端储存模式和小端储存模式。
// 判断当前机器的字节序
#include 
#include 
#include int main() 
{int i = 1;				// 它的十六进制是 0x 00 00 00 01char* p = &i;if (*p == 1){printf("小端");}else{printf("大端");}return 0;
}
下面是个十分巧妙的用法,着重关注一下!!!!!!!!!!!!!!!!!!!!!!!!
#include 
#include 
#include check_sys()
{// 返回1,小端// 返回0,大端int a = 1;return *(char*)&a;
}
// 指针类型的意义
// 1. 指针类型决定了指针解引用能够访问几个字节:char *p; *p 访问了一个字节,int *p;*p,访问了4个字节
// 2. 指针类型决定了指针+1,-1,加的或者减的是几个字节:char *p; *p 跳过一个字节,int *p;*p,跳过4个字节int main() 
{// 返回1,小端// 返回0,大端int ret = check_sys();if (ret == 1){return 1;}else{return 0;}
}

浮点型在内存中的储存解析

浮点数包括:float、double、long double类型
浮点数表示的范围:float.h文件当中

IEEE 754规定:对于32位的浮点数,最高的1位是符号位S,接着的8位是制数E,身下的23位为有效数字M。
在这里插入图片描述

IEEE754规定:对于64为浮点数,最高的1位是符号位S,接着11位是指数E,剩下的52位为有效数字M
在这里插入图片描述

这玩意有点难,看链接 单双精度浮点数详解

指针进阶

指针基本概念:1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间;2. 指针的大小是固定的4/8个字节(32位平台/64位平台);
#include 
#include 
#include void test(int arr[])
{printf("%d\n", sizeof(arr) / sizeof(arr[0]));	// 因为arr是个指针,所以指针大小4个字节除以元素大小4个字节等于1,但是电脑实际显示2// 那是因为平台是64位的,所以指针大小是8个字节,所以得到2
}int main() 
{int arr[10] = { 0 };test(arr);return 0;
}
	3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限;4. 指针的运算。
  • 字符指针

      用法一
    
#include 
#include int main() 
{char ch = 'w';char* pc = &ch;		// pc就是字符指针return 0;
}
	用法二:
#include 
#include int main() 
{char arr[] = "abcdef";char* pc = arr;printf("%s\n", arr);printf("%s\n", pc);return 0;
}
	用法三:
#include 
#include int main() 
{// 这句话的意思不是把字符串放到指针里,是把a的地址赋给了pchar* p = "aabcdef";printf("%c\n", *p);		// 结果aprintf("%s\n", p);		// 结果aabcdefreturn 0;
}
  • 指针数组

      主语是数组,用来存放指针
    
#include 
#include int main() 
{int arr[10] = { 0 };	// 整型数组char ch[5] = { 0 };		// 字符数组int* parr[4];	// 存放整形指针的数组 - 指针数组char* pch[5];	// 存放字符指针的数组 - 指针数组return 0;
}
#include 
#include int main() 
{int a = 10;int b = 20;int c = 30;int d = 40;int* arr[4] = { &a, &b, &c, &c };// 低级用法int k = 0;for (k = 0; k < 4; k++){printf("%d ", *(arr[k]));}printf("\n");// 常见用法int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };int* parr[] = { arr1,arr2,arr3 };int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){printf("%d ", *(parr[i]+j));}printf("\n");}return 0;
}
  • 数组指针

      数组指针他是个指针
    
#include 
#include int main() 
{int* p = NULL;  // 整型指针 - 指向整型的指针 - 可以存放整型的地址char* pc = NULL;// 字符指针 - 指向字符的指针 - 存放字符的地址// int arr[10] = {0};// arr - 首元素地址// &arr[0] - 首元素的地址// &arr - 数组的地址// 数组指针 - 指向数组的指针 - 存放数组的地址int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* (*p)[10] = &arr;	// p就是数组指针,存放数组的地址return 0;
}
#include 
#include int main() 
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int(*pa)[10] = &arr;	// 把整体的数组扔给pa了// 写法一int i = 0;for (i = 0; i < 10; i++){printf("%d ",(*pa)[i]);}// 写法二for (i = 0; i < 10; i++){printf("%d ", *(*pa + i));}// 写法三:推荐写法int* p = arr;for (i = 0; i < 10; i++){printf("%d ", *(p + i));}return 0;
}
// 数组指针的正确用法
#include 
#include void print1(int arr[3][5], int x, int y)
{int i = 0;int j = 0;for (i =0;i<x;i++){for (j = 0; j < y; j++){printf("%d ", arr[i][j]);}printf("\n");}
}// 参数是指针的形式
void print2(int(*p)[5], int x, int y)
{int i = 0;int j = 0;for (i = 0; i < x; i++){for (j = 0; j < y; j++){printf("%d ", *(*(p + i) + j));// printf("%d ", (*(p + i))[j]);	这种写法也可以// 写法还有好多,这里忽略}printf("\n");}
}int main() 
{int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };print1(arr, 3, 5);	// arr - 数组名 - 首元素地址 - 第一行的地址print2(arr, 3, 5);return 0;
}
  • 数组传参和指针传参

      写代码时,难免需要把数组或者指针传给函数,那么函数的参数改如何设计呢
    

一维数组传参

#include 
#include void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}
void test(int* arr[20])
{}
void test(int** arr)
{}int main() 
{int arr[10] = { 0 };int *arr2[20] = { 0 };test(arr);test(arr2);return 0;
}

二位数组传参

#include 
#include void test(int arr[3][5])
{}
void test(int arr[][]) // 不OK,列不能省略
{}
void test(int arr[][5])
{}
// 二位数组传参,函数形参的设计只能省略第一个[]的数字void test(int* arr) // 不ok,数组名是首元素地址是个一维数组,不能用整型指针
{}
void test(int* arr[5]) // 不OK,5个元素,类型是指针,但是传过来的是数组
{}
void test(int(*arr)[5])
{}
void test(int** arr) // 不OK,它应该存放一级指针变量的地址,但传过来的是数组
{}int main() 
{int arr[3][5] = {0};test(arr);return 0;
}

一级指针传参

#include 
#include void print(int* p, int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d\n", *(p + i));}
}int main() 
{int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };int* p = arr;int sz = sizeof(arr) / sizeof(arr[0]);print(p, sz);return 0;
}

二级指针传参

#include 
#include void test(int** ptr)
{prinf("%d\n", **ptr);
}int main() 
{int n = 10;int* p = &n;int** pp = &p;test(pp);test(&p);return 0;
}
  • 函数指针

      指向函数的指针,存放函数地址的一个指针
    
#include 
#include int Add(int x, int y)
{int z = 0;z = x + y;return z;
}int main() 
{int a = 10;int b = 20;printf("%d\n", Add(a, b));// &Add 和 Add 都是函数的地址printf("%p\n", &Add);	// Add函数的地址printf("%p\n", Add);	// 他们两个一样int (*pa)(int, int) = Add;	// pa 是个指针,指向函数,也就是函数指针printf("%d\n", (*pa)(2, 3));	// 结果: 5,证明pa里面确实是函数的地址// *pa中的*就是个摆设,写多少个、或者不写都不影响return 0;
}

在这里插入图片描述

  • 函数指针数组

      表现形式:int (*parr1[10])()用途:转移表(计算加减乘除)
    
#include 
#include int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}int main() 
{int a = 10;int b = 20;int* arr[5];	// 这是个指针数组int(*pa)(int ,int) = Add; // 发现这个里面可以存Add、Mul、Sub,这个时候就需要一个数组 - 函数指针数组int (*parr[4])(int, int) = { Add, Sub, Mul }; // 函数指针数组// 函数指针数组 调用方法int i = 0;for (i = 0; i < 3; i++){printf("%d\n", parr[i](2, 3));}return 0;
}
  • 指向函数指针数组的指针

      指向函数指针数组的指针是一个 指针 指向一个 数组,数组的元素都是 函数指针
    
#include 
#include int Add(int x, int y)
{return x + y;
}int main() 
{int arr[10] = { 0 };int (*p)[10] = &arr;int (*pfArr[4])(int, int);	// 函数指针的数组int(*(*ppfArr[4])) = &pfArr; // 指向函数指针数组的指针return 0;
}
  • 回调函数

回调函数就是一个通过函数指针调用的函数。如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,就被称为回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。简单来说,就是通过函数指针调的一个函数。

#include 
#include void print(char* str)
{printf("%s===", str);
}void test(void(*p)(char*))
{printf("test\n");p("hello");
}int main() 
{test(print);
}
#include 
#include 
#include void bubble_sort(int arr[], int sz)
{int i = 0;printf("%p\n", &arr);for (i = 0;i < sz - 1;i++){int j = 0;for (j = 0;j < sz-1-i;j++){if (arr[j] > arr[j + 1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}
}struct Stu
{char name[20];int age;
};int main() 
{int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);struct Stu s[3] = { {"张三", 20}, {"李四", 30},{"王五", 90} };	// 如果用结构体中的age去排序,会发现不够通用bubble_sort(arr, sz);		// 因为这块数组名是首元素地址,其实传过去的是个地址,是实参,所以数组才会被正确修改int i = 0;printf("%p\n", &arr);for (i = 0;i < sz;i++){printf("%d ", arr[i]);}return 0;
}
qsort 介绍用法: qsort  (数组名,数组元素个数,元素字节,比较函数)void * p = &a; 		1. 无类型指针,没有具体类型,可以接受任何类型。2. 不能进行解引用,如果想要解引用,应该对其进行强制类型转换。3. 不能进行加减乘除、++、--等操作。

  • 指针和数组面试题解析

      初步复习
    
#include 
#include 
#include int Add(int x, int y)
{return x + y;
}int main() 
{int* arr[10];	// 指针数组int* (*p)[10];	// 数组指针int (*pAdd)(int, int) = Add;		// 函数指针int num = (*pAdd)(2, 3);int num1 = pAdd(4, 5);printf("%d\n", num);printf("%d\n", num1);int (*pArr[5])(int, int);		// 函数指针的数组int(*(*pArr)[5])(int, int) = &pArr;		// 指向函数指针数组的指针// ppArr = &pArr;return 0;
}

写不动了、开摆


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部