#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//头文件,仅标注一次
e.g.猜数字游戏
//e.g.猜数字游戏
//1.电脑生成一个随机数
//2.猜数字
//3.循环玩
#include<stdlib.h>
#include<time.h>
void menu()
{
printf("************************\n");
printf("***1.play 0.exit ***\n");
printf("************************\n");
}
void game()
{
//printf("猜数字\n");
//1.生成一个随机数
int ret = 0;
int guess = 0;
//rand()-->生成随机数0~32,767
// rand_max=32,767(右键)转到定义--> #define rand_max 0x7fff(十六进制)
/*时间戳
计算机当前的时间 - 计算机的起始时间(1970.01.01 08:00:00) = (xxxx)秒
拿时间戳来设置随机数的生成起点:time()函数
函数原型:time_t time(time_t* timer);
time_t(类型)
typedef __time64_t time_t;
typedef __int64 __time64_t;
*/
ret = rand()% 100 + 1;//但是这样写随机数顺序都一样
//printf("%d\n", ret);
//2.猜数字
while (1)
{
printf("猜数字吧:>");
scanf("%d", &guess);
if (guess > ret)
printf("猜大了\n");
else if (guess < ret)
printf("猜小了\n");
else
{
printf("恭喜你,猜对了!\n");
break;
}
}
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
//随机数生成起点整个程序设置一次就可以,不需要频繁调用
do {//游戏至少进行一次
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);//不为0,就循环(nb)
return 0;
}
goto语句
可以随意滥用的goto语句和标记跳转的标号
主要用途(常见用法):
终止程序在某些深度嵌套的结构的处理过程,例如一次跳出两层或多层循环
for(...)
{
for(...)
{
for(...)
{
if(disaster)
goto error;
}
}
}
...
error:
if(disaster)
...//处理错误情况
e.g.一个关机程序
#include<string.h>
#include<stdlib.h>
int main()
{
char input[10] = { 0 };
system("shutdown -s -t 60");//关机程序
while (1)
{
again:
printf("电脑将在1分钟内关机,如果输入:我是猪,"
"就取消关机!\n请输入:>");
scanf("%s", input);
if (0 == strcmp(input, "我是猪"))
{
system("shutdown -a");//取消关机
break;
}
else
{
goto again;//循环
}
}
return 0;
}
函数
1.库函数
2.自定义函数
C语言常用库函数:
1)I/O函数
2)字符串操作函数
3)字符操作函数
4)内存操作函数
5)时间/日期函数
6)数字函数
7)其他...
char* strcpy(char* destination, char* source);
包含结束的'\0'也要拷贝
memory--内存;set--设置:
void* memset(void* ptr, int value, size_t num);
把ptr所指向的空间的前num个字节的内容设置成指定的value值
//例.memset()
#include<string.h>
int main()
{
char arr[] = "hello world";
memset(arr, '*', 5);
printf("%s\12", arr);
return 0;
}
学会如何使用库函数?
库函数查询工具:
- MSDN(Microsoft Developer Network)
- www.cplusplus.com
- http://en.cppreference.com
自定义函数
交换函数:
函数的参数:
1.实际参数(实参)
真实传递给函数的参数。
实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时
他们都必须有确定的值,以便传递给形参。
2.形式参数(形参)
函数名后括号中的变量。
因为形式参数中有在函数被调用的过程中才实例化(分配内存单元),所以叫形参。
形参当参数调用完成之后自动销毁,因此形参只在函数中有效。
当实参传递给形参时,形参实际是实参的一份临时拷贝(形参与实参毫无联系)
对形参的修改不会影响实参。
函数调用:
1.传值调用
函数的形参和实参分别占有不同的内存块,对形参的修改不会影响实参
2.传址调用
把函数外部创建的变量的内存地址传递给函数参数
这种传参方式可以让函数和函数外的变量建立起真正的联系,即函数内部可以直接操作函数外部的变量
3.区别
如果只想获取值(get_max()),选传值调用
如果想利用函数修改值,选传址调用
※数组传参传的是元素首地址
※本质上arr是个指针,因此在函数内部,sizeof(arr) = 1
//交换函数
void swap1(int a, int b)//传值调用
{//形参
int temp = 0;
temp = b;
b = a;
a = temp;
printf("x = %d y = %d\n", a, b);//5 10
}
void swap2(int* pa, int* pb)//传址调用
{
int temp = 0;
temp = *pa;
*pa = *pb;
*pb = temp;
}
int main()
{
int x = 10;
int y = 5;
//swap1(x, y);//10 5
swap2(&x, &y);//5 10
//实参
printf("x = %d y = %d\n", x, y);
return 0;
}
函数的嵌套调用与链式访问
函数与函数之间可以有机的结合
链式访问:把一个函数的返回值作为另一个函数的参数
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
//printf()的返回值是打印字符的个数
//2 43
//1 2 43 -->4321
return 0;
}
函数的声明与定义
int Add(int, int);//声明,适用于模块化编程
在Add.h头文件里声明函数Add();
在Add.c源文件里定义函数
在test.c源文件里调用#include"Add.h"
//Add.h
#pragma once
#ifndef __ADD_H__//函数名可换if not define add.h
#define __ADD_H__
int Add(int, int);
#endif // !
/*
如果没有定义过,就包含这个头文件了
如果定义过了,就不要下面的代码了
---防止头文件重复包含
*/
函数声明:
1.告诉编译器有一个函数叫什么,参数类型是什么,返回类型是什么,但具体是否存在无关紧要;
2.函数的声明一般在头文件中,先声明后用
函数定义:
指函数的具体实现,交代函数的功能实现
函数递归(recursion)
函数递归(recursion)
程序调用自身的编程技巧。
只需要少量程序就可以描述除解题过程所需要的多次重复运算,大大减少了代码量
递归---把大事化小
必要条件 * 2
1存在限制条件,当满足这个限制条件时,递归便不再继续
2每次递归调用之后越来越接近这个限制条件
递归常见错误:
栈溢出(stack overflow)
任何一次函数调用都会在栈区占用一块空间,最后栈区无空间,就溢出
int main()
{
printf("hehe\12");
main();//死循环
return 0;
}
/※e.g.输入无符号数字1234,然后得到输出1 2 3 4
//※e.g.输入无符号数字1234,然后得到输出1 2 3 4(nb)
void print(int num)
{
if (num > 9) { //1234 12
print(num / 10);//123 1
}
printf("%d ", num % 10);//1
}
int main()
{
unsigned int num = 0;
printf("请输入一个大于9的数字:>");
scanf("%d", &num);
print(num);
return 0;
}
递归与迭代
求n的阶乘
//求n的阶乘
int Fac1(int n)
{
int i = 0;
int ret = 1;
for (i = 1; i <= n; i++)
ret *= i;
return ret;
}
int Fac2(int n)
{
if (n <= 1)
return n;
else
return Fac2(n - 1) * n;//nb
}
int main()
{
int n = 0;
printf("输入n,求n的阶乘:>");
scanf("%d", &n);
printf("%d! = %d\12", n, Fac1(n));
printf("%d! = %d\12", n, Fac2(n));
return 0;
}
e.g.2 编写函数,不允许创建临时变量,求字符串长度
#include<string.h>
int my_strlen(char* str)
{
int count = 0;//用了临时变量
while (*str != '\0')
{
count++;
str++;
}
return count;
}
//函数1会影响函数2,用2时屏蔽掉!
int my2_strlen(char* str)
{
if (*str != '\0')
return 1 + my2_strlen(str + 1);
else
return 0;
}
int main()
{
char arr[] = "I lost myself";
//int len = strlen(arr);
//int len = my_strlen(arr);
int len = my2_strlen(arr);
printf("%d", len);
return 0;
}
e.g.求第n个斐波那契数(不考虑溢出)
斐波那契数列-->前两个数之和等于第三个数
Fib(n) = 1, n <= 2;
Fib(n) = Fib(n - 1) + Fib(n - 2), n > 2;
1 1 2 3 5 6 13 21 34 55 89...
int Fib1(int n)//but 效率低,缺陷大
//算第40个,要跑39,000,000+次???
{
if (n <= 2)
return 1;
else
return Fib1(n - 1) + Fib1(n - 2);
}
//时间复杂度O(2^n)
int Fib2(int n)//迭代循环
{
int a = 1;
int b = 1;
int c = 1;
if (n <= 2 )
return 1;
while (n > 2)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int Fib3(int n)//数组(易越界)
{
int fibs[100] = { 0,1,1 };
int i = 2;
for (i = 2; i <= n; i++)
{
fibs[i] = fibs[i - 1] + fibs[i - 2];
}
return fibs[n];
}
//时间复杂度:O(n)
int main()
{
int n = 0;
printf("输入n,求第n个斐波那契数;>");
scanf("%d", &n);
//TDD-->测试驱动开发
//先写怎么用,再去具体实现
printf("%d", Fib3(n));
return 0;
}
选递归还是选循环?
first of all,保证正确 ,其次可任选
递归满足了两个限制条件,就不会溢出嘛?
Not sure.
//栈溢出
void test(int n)
{
if (n < 10000)//stack overflow
test(n + 1);
}
int main()
{
test(1);
return 0;
}
函数递归经典题目案例
1.汉诺塔
2.青蛙跳台阶(广联达原题)
---《剑指offer》67道笔试题
1.汉诺塔
思路:n个盘子首先以大小顺序依次叠在A柱上,借助B柱实现从A柱平移到C柱上。难点在于每次移动都必须保证每根柱子上的盘子排序是下面的盘子比上面的大。
例:n=3时,A柱从上到下盘子编号为1,2,3,首先1盘-->C,2盘-->B,然后C上的1盘可以-->B,此时A只有3盘,B依次有1、2盘,C没有盘子。所以可以将A的3盘-->C,此时,A柱0盘。再将B柱的1盘-->A,那么B柱的2-->C,A的1-->C,移动结束。
借助递归,省略了具体实现细节,从广义上解释移动过程——
将A上前n-1个盘子借助A、C移动到B上
把A上第n个移动到C上
将B上前n-1个盘子借助B、A移动到C上
//汉诺塔
void print(char a, char b)
{
printf("%c-->%c\n", a, b);//输出移动过程
}
//递归,省略具体实现细节,nb
void Hanoi(int n, char a, char b, char c)
{
if (n == 1)
print(a, c);//A-->C
else
{
Hanoi(n - 1, a, c, b);
//将A上前n-1个盘子借助A、C移动到B上
print(a, c);
//把A上第n个移动到C上
Hanoi(n - 1, b, a, c);
//将B上前n-1个盘子借助B、A移动到C上
}
}
int main()
{
int n = 0;
printf("请输入盘子的个数:>");
scanf("%d", &n);
printf("移动次序为:>\12");
Hanoi(n, 'A', 'B', 'C');
return 0;
}
2.青蛙跳台阶
一只青蛙一次可以跳上 1 级台阶,也可以跳上2 级。
求该青蛙跳上一个n 级的台阶总共有多少种跳法?
n = 1--->1
n = 2--->2
n = 3--->3
n = 4--->5
n = 5--->8
...
1,2,3,5,8...Fibs?
We can see, 第n次的跳法时第n-1次和第n-2次的和
int Fib1(int n)
{
//递归(效率低,运算大量重复)
if (n <= 2)
return n;
else
return Fib1(n - 1) + Fib1(n - 2);
}
int Fib2(int n)
{
//循环
int a = 1;
int b = 2;
int c = 0;
if (n == 1)
return a;
else if (n == 2)
return b;
else
{
int i = 0;
for (i = 3; i <= n; i++)
{
c = a + b;
a = b;
b = c;
}
return c;
}
}
int main()
{
int n = 0;
printf("请输入台阶数n:>");
scanf("%d", &n);
printf("共有%d中跳法\n", Fib2(n));
return 0;
}
数组
- 1.一维数组的创建和初始化
- 2.一维数组的使用
- 3.一维数组在内存中的存储
- 4.二维数组的创建和初始化
- 5.二维数组的使用
- 6.二维数组在内存中的存储
- 7.数组作为函数参数
- 8.数组的应用实例1:三子棋
- 9.数组的应用实例2:扫雷游戏
1.数组的创建
一组类型相同的数据元素的集合
int arr[10];
char arr1[5];
int n = 5;
char arr[n];//err
初始化
#include<string.h>
int main()
{
int arr[10] = { 1,2,3,4,5 };//不完全初始化
char arr2[5] = "ab";
//{a, b, '\0', 0, 0}
//arr2和arr3存储方式不同
char arr3[5] = { 'a','b'};//strlen是随机值
//{a, b, 0, 0, 0}
char arr4[5] = { 'a',98 };
//{a, b, 0, 0, 0}
char arr5[] = "abcdef";
char arr6[] = { 'a','b','c','d','e','f' };
printf("%d\12%d\xa", strlen(arr2), strlen(arr3));
printf("%d\n", strlen(arr4));
//printf("%d\12%d\xa", strlen(arr5), strlen(arr6));
printf("%d\n", sizeof(arr5));//所占空间大小
printf("%d\12", strlen(arr5));//字符串长度
//arr6长度时随机值
return 0;
}
strlen()和sizeof()的关系
1.strlen和sizeof没什么关联
2.strlen只能求字符串长度
3.sizeof计算变量、数组、类型的大小---单位是字节
4.strlen是库函数---头文件string.h; sizeof是操作符
#include<string.h>
int main()
{
char arr1[] = "abc";
char arr2[] = { 'a','b','c' };
printf("sizeof(arr1) = %d\n", sizeof(arr1));
//4-->a,b,c,\0
printf("sizeof(arr2) = %d\n", sizeof(arr2));
//3-->a,b,c
printf("\12strlen(arr1) = %d\n", strlen(arr1));//3
printf("strlen(arr2) = %d\n", strlen(arr2));//3,随机值。找'\0'
return 0;
}
2.[]--->下标引用操作符
字符数组遍历形式:
for (i = 0; i < strlen(arr); i++)
strlen(arr)默认返回无符号整型值
(int)strlen(arr)-->强制类型转换会警告
修改如下:
int len = strlen(arr);
for (i = 0; i < len; i++)
整型数组遍历形式:
int arr[] = { 1,2,3,4,5,6 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
3.内存分布
数组在内存中连续存放
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
printf("&arr[%d]=%p\n", i, &arr[i]);
return 0;
}
4.二维数组创建
二维数组是由一维数组组成的一维数组
5.二维数组通过下标访问
6.内存分布
int main()
{
//4.
//数组创建
int arr11[3][4];//三行四列
char arr21[3][5];
double arr31[3][6];
//初始化
int arr1[3][4] = { 1,2,3,4 };
int arr2[3][5] = { {2,3},{3,4} };
int arr3[][3] = { {2,3},{3,4} };//行号可省,列号不可
//5.
//二维数组通过下标访问
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
printf("%d ", arr2[i][j]);
printf("\12");
}
printf("\12");
//6.
//内存分布
int arr[3][4] = { {1,2,3},{2,3,4},{3,4,5} };
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
printf("&arr[%d][%d]=%p\n", i, j, &arr[i][j]);
}
return 0;
}
冒泡排序
//e.g.设计一个函数实现冒泡排序,将一个整型数组排序
void Bubble_Sort(int* arr, int sz)
//arr是数组,对数组传参,传的是首元素地址&arr[0]
{
//确定bubble排序的趟数
int i = 0;
int count = 0;
for (i = 0; i < sz - 1; i++)
{
int flag = 1;
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = 0;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = 0;
//本趟排序的数据不完全有序,进行交换
count++;
}
}
if (flag == 1)//有序,跳出循环,冒泡结束
break;
}
printf("共排序%d次\n", count);
}
int main()
{
//int arr[] = { 9,8,7,6,4,5,3,2,1 };//36
int arr[] = { 1,2,3,4,5,6,7,8,9 };//36
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
//对数组进行升序排序
Bubble_Sort(arr, sz);
for (i = 0; i < sz; i++)
printf("%d ", arr[i]);
printf("\12");
return 0;
}
数组名不是首元素地址?
数组名是什么?--->首元素地址
两个例外:
1.sizeof(arr)
数组名表示整个数组,sizeof(arr)计算的是整个数组的大小,单位是字节
2.&arr
数组名表示整个数组,&arr取出的是整个数组的大小
int main()
{
int arr[] = { 1,2,3,4,5 };
printf("%p\n", arr);//数组首地址
printf("%p\n", &arr[0]);//数组首地址
printf("%p\n", &arr);//整个数组的地址,也从arr[0]开始
printf("%p\n", *arr);//数组名解引用
int arr1[10] = { 0 };
printf("%d\n", sizeof(arr1));//10 * 4
return 0;
}
//e.g.地址比较
int main()
{
int arr[] = { 1,2,3,4,5,6,7 };
printf("arr = %p\n", arr);
printf("arr+1 = %p\n\n", arr + 1);//+4
printf("&arr[0] = %p\n", &arr[0]);
printf("&arr[0] + 1 = %p\n", &arr[0] + 1);//+4
printf("&arr = %p\n", &arr);
printf("&arr + 1 = %p\n", &arr + 1);//+28
return 0;
}