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语言基础_第1张图片

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

第一个C语言程序

  1. 创建Project
  2. 创建文件
    C语言当中,文件后缀如下:
    .c – 源文件
    .h – 头文件
  3. 写代码:
// 第一段代码
// standard input output
#include 

// int是整型,main前面的int表示main函数返回一个整形值
int main() // 主函数-程序的入口,主函数有且仅有一个
{
	// printf是打印函数,是C语言的库函数,所需需要导入stdio.h
	printf("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(不可变)
枚举常量 --enum enum Sex{ MALE,FEMALE,SECRET};(不可变)

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

// const 常变量介绍
#include 
#include 

#define A 10

int 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,打印结果:abc
	printf("%s\n", arr2); 	// arr2中有三个元素,a,b,c,打印结果:abc烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫贴-戽?
	printf("%d\n", strlen(arr1));	// 结果:3
	printf("%d\n", strlen(arr2));	// 结果:是个随机值,直到遇到 '\0' 才会停止
	return 0;
}

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

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

转义字符

转义字符串 说明
? 在书写连续多个问号时使用,防止他们被解析成三字母词
用于表示字符常量’
" 用于表示一个二祖父穿内部的双引号
\\ 用于表示一个反斜杠,防止它被解释为一个转义序列符
\a 警告字符,蜂鸣
\b 退格符
\f 进纸符
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ddd ddd表示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");
	else
		printf("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    do
      double    else    enum   extern  float     for   goto  if   int  long
      register  return  short  signed  sizeof   static  struct  switch  typedef
      union  unsigned   void  volatile  while 
    

    关键字解释

    关键字 解释
    auto 本来局部变量在前面有auto,因为都有,就被省略了,例如:auto int num = 2;
    break 跳出循环
    case swich case 语句中的
    char 字符类型
    const 常变量
    continue 继续
    default 默认,switch case 语句中的
    do do while 循环
    double 双精度浮点类型
    else if 语句中的
    enum 枚举常量
    extern 引入外部符号
    float 单精度浮点数
    for 循环语句
    goto goto 语句
    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 结构体关键字
    switch switch 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;
	else
		return 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);	// 结果:20

	return 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:9
	return 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;		// 重新起了名字叫Stu

int 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("老年");
	else
		printf("牛逼");

	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("过寿");
   	}
   else
   	printf("牛");
   
   // { } 把 if 和 else 隔开
   if (age == 4)
   {
   	printf("aaa");	// 这个hello会被打印
   	if (age == 55)
   		printf("bbb");	// 这个hello会被打印
   }
   else
   	printf("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 调用 D
      2. 链式调用
      	就是把一个函数的返回值作为另一个函数的参数,示例如下:
    
#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;
}

C语言基础_第2张图片

  • 二维数组的创建、初始化、使用
#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;
}

C语言基础_第3张图片
C语言基础_第4张图片

  • 数组作为函数参数

      以冒泡排序为例(相邻元素比较大小,假设有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);
}

C语言基础_第5张图片

常用技巧

  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,通常应用于函数的返回类型、函数的参数、指针类型

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

C语言基础_第6张图片

大小端字节序介绍及判断

什么是大端小端:
	大端(储存)模式:是指数据的低位,保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
	小端(储存)模式:是指数据的低位,保存在内存的低地址中,而数据的高位,保存在内存的高地址中。
为什么要有大小端:
	因为存的时候可以随便存,但是要用的时候还要再拿出来,不规定格式拿出来就乱了。
	现在的电脑,几乎都是按照大小端来存储的。
	在计算机系统当中,以字节为单位,一个字节8bit,C语言中除了8bit的char类型外,还有16bit的short类型,32bit的long型(看具体的
	编译器),另外,对于位数大于8位的处理器,例如16位或者32位处理器,由于寄存器宽度大于一个字节,那么必然存在着多个字节的
	安排问题,因此就导致了大端储存模式和小端储存模式。
// 判断当前机器的字节序
#include 
#include 
#include 

int main() 
{
	int i = 1;				// 它的十六进制是 0x 00 00 00 01
	char* 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。
C语言基础_第7张图片

IEEE754规定:对于64为浮点数,最高的1位是符号位S,接着11位是指数E,剩下的52位为有效数字M
C语言基础_第8张图片

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

指针进阶

指针基本概念:
	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. 指针的运算。
  • 字符指针

  • 数组指针

  • 指针数组

  • 数组传参和指针传参

  • 函数指针

  • 函数指针数组

  • 指向函数指针数组的指针

  • 回调函数

  • 指针和数组面试题解析

你可能感兴趣的:(C,c语言,开发语言)