目录
函数与数组
本篇博客总结一下近十余天的学习收获。主要涉及函数以及数组的知识。
零散知识点
1、printf叫作“格式化打印”,print format。%d叫作“格式化字符串”。
2、使用strlen函数时需要包含对应的库函数。C语言内置的库函数其实非常少(几百个)。相比之下,Java、Python等更加现代的语言,支持的库函数要更加丰富和庞大。
3、变量/函数 常见的命名风格:
(a)小驼峰命名法:toGuess
(b大小驼峰命名法:ToGuess
©蛇形命名法:to_guess
(d)脊柱命名法:to-guess
(e)匈牙利命名法:iToGuess
C/C++中蛇形命名法也比较常见,其它场景下都是驼峰命名法最常见。
4、计算机中,使用一个整数来表示时间,以1970年1月1日0时0分0秒作为基准时刻。以现在时间减去基准时间的秒数之差表示时间。这就是“时间戳”的概念。
5、C语言中知识goto语句,表示“无条件跳转”。
6、linux发行版:各个公司使用linux免费内核(操作系统的核心功能程序),进行自己的定制化开发,加上自己的一些应用。如:Redhat、Debian、Centos、Ubuntu等。
7、编译器会对编写的代码进行优化:在不改变整体逻辑等价的前提下,调整二进制指令的顺序和内容,让调整后的代码执行效率更高。优化会影响bug定位,故调试的时候需要把优化选项关闭或者级别降低,O0不开,O3最大优化(vs默认没有开优化)。
8、在C语言中,整数/整数 结果仍是整数,小数部分被舍去。Java,C++,Go等语言也是如此。但JavaScript,Python3,Dart等语言1/2=>0.5.
随机数与随机种子
计算机中生成的随机数通常都是“伪随机”。
使用rand()函数生成随机数时,会出现得到的随机数序列是固定的序列的问题,例如第一个元素始终是42,第二个事68,第三个是35…
生成随机数序列的初始值,称为“随机种子”。
故想每次程序启动得到不同的随机数序列,就需要设置不同的随机种子。
一般使用时间作为随机种子,例如:srand(time(0));
函数
函数就是一个可以被重复利用的代码片段,避免重复代码被反复拷贝,让代码更简洁,更容易理解。
函数(不局限在C语言中)大概分为几类:
1、标准库函数. printf/scanf/strlen/strcmp
2、第三方库函数. scanf_s Sleep
3、自定义函数.
函数调用的执行过程:
代码执行到函数调用位置的时候,就会进入到函数内部执行,同时对参数进行赋值,(按照位置顺序,分别把实参赋值(拷贝)给形参)。然后在函数内部按照顺序从上到下执行。遇到return语句,则函数结束。
函数结束之后将回到函数的调用位置,继续往下执行。
嵌套调用:函数里调用别的函数
链式访问:函数的返回值作为其它函数的参数
一个函数可以被调用多次,每次调用的时候,都可以传递不同的参数。
实参和形参的关系,只在参数传递的这一瞬间,用实参对形参进行初始化。
这之后,形参和实参之间没有关系了。
很多编程语言天然就支持函数内部访问函数外部的变量(比如JS,Java算部分支持,C++有特殊手段,变量捕获语法能支持),但是C语言不行,C语言的函数内部只能访问函数中定义的局部变量和全局变量。PS:不建议使用全局变量。
因此,需要使用指针来间接访问外部的变量。
C语言中调用函数时,必须保证函数的定义或者声明在调用位置代码的上方(声明一般放在代码最上方)。一个工程中,函数只能定义一次,但是声明可以有很多次。定义有多份就会编译失败。且由于经常要同时声明多个函数,所以放在一个.h文件中,再通过#include的方式引入到.c文件中。例如:
#include 从系统目录中查找头文件
#include “xxx” 从当前项目目录中查找头文件,再从系统目录中查找头文件
#include本质是“文本替换”,即拷贝替换。
可以使用
#ifdef TEST_H_
#define TEST_H_
//函数的声明
int Add(int x,int y);
#endif
防止头文件被重复包含(不建议这么写了,太麻烦)。
#pragma once这种写法可以起到一样效果,简单且不容易错。
递归即函数调用其本身。调试递归程序,必须要重点关注函数的“调用栈”。
不使用循环/不创建局部变量实现求阶乘→递归(特别适用动态规划问题)
数组
数组批量创建一组相同类型的变量。C++、Java、Go也是入如此,但是Python、JS、PHP不要求相同类型。
C语言中的字符串是一种特殊的“字符数组”,必须是以’\0’(即ASCII值为0的字符)作为结束标记。如果某个字符数组中,没有’\0’,那么这个字符数组就不能称为字符串。PS:字符串长度不计算\0,数组的元素个数计算\0。
一个未初始化的数组,里面的元素内容本质上是VS自动填充的一些无意义的数据。若抛开VS,将是栈空间上的残留值。
在Java或者Go中,对于未手动初始化的变量,会被编译器自动初始化为0值。
定义数组时,[]内只能写常量(字面值、枚举、宏定义),不可使用变量和const。
C语言中,针对数组取下标操作,若越界,产生的效果称为“未定义行为”,运行结果不可预期。数组下标越界,本质上是“访问了非法内存”。
[]操作元素有读写操作,读就是获取变量的值,写就是修改变量的值。
对于一维数组,元素都是处在连续的内存空间上。数组的“维数”可以有很多,但是实际开发中很少使用高维数组。
代码
近期编写的代码如下:
打印素数(穷举法)
打印100~200之间的素数
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:4996)
int main()
{
int count = 0;
for (int i = 1; i <= 100; i++){
if (i % 10 == 9){
count++;
}
if (i / 10 == 9){
count++;
}
}
printf("%d\n", count);
system("pause");
return 0;
}
数字9出现的次数
计算 1 到 100 的所有整数中出现多少个数字9
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:4996)
int main()
{
int count = 0;
for (int i = 1; i <= 100; i++){
if (i % 10 == 9){
count++;
}
if (i / 10 == 9){
count++;
}
}
printf("%d\n", count);
system("pause");
return 0;
}
分数求和
计算1/1-1/2+1/3-1/4+1/5 …… + 1/99 - 1/100 的值,打印出结果
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:4996)
int main()
{
double result = 0;
for (int i = 1; i <= 100; i++){
if (i % 2 != 0){
result += 1 / (double)i;
}
else{
result -= 1 / (double)i;
}
}
printf("%f\n", result);
system("pause");
return 0;
}
求最大值
求10 个整数中最大值
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:4996)
int main()
{
int arr[] = { 85, 21, 33, 65, 14, 94, 54, 69, 75, 24 };
int max = arr[0];
for (int i = 0; i < 10; i++){
if (arr[i]>max)
max = arr[i];
}
printf("最大值是:%d\n", max);
system("pause");
return 0;
}
猜数字游戏
#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#include <time.h>
#pragma warning(disable:4996)
void Game(){
int random_num = rand()%100;
int input;
while (1){
printf("请输入数字:");
scanf("%d", &input);
if (input > random_num){
printf("猜大了\n");
}
else if (input < random_num){
printf("猜小了\n");
}
else{
printf("猜对了\n");
break;
}
}
}
int main()
{
int option = -1;
srand(time(0));
while (1)
{
printf("***********************\n");
printf("********1.play*********\n");
printf("********0.exit*********\n");
printf("***********************\n");
scanf("%d", &option);
if (option == 1){
Game();
break;
}
else if (option == 0){
break;
}
else{
printf("输入有误!\n");
Sleep(300);
system("cls");
}
}
system("pause");
return 0;
}
交换两个整数
输入正确用户名和密码后附带转圈加载效果,输错3次锁5秒倒计时。
#include <stdio.h>
#include <Windows.h>
#include <math.h>
#pragma warning(disable:4996)
void Swap(int *num1,int *num2){
int tmp = 0;
tmp = *num1;
*num1 = *num2;
*num2 = tmp;
}
int main(){
int num1, num2;
int *p1 = &num1;
int *p2 = &num2;
printf("请输入两个数#");
scanf("%d %d",&num1,&num2);
printf("交换前:%d %d\n", num1, num2);
Swap(p1,p2);
printf("交换后:%d %d\n", num1, num2);
system("pause");
return 0;
}
乘法口诀表
打印乘法口诀表,口诀表的行数和列数自己指定
#include <stdio.h>
#include <Windows.h>
#include <math.h>
#pragma warning(disable:4996)
void MulTable(int num){
for (int i = 1; i <= num; i++){
for (int j =1; j <= i; j++)
printf("%d*%d=%d\t", i, j, i*j);
printf("\n");
}
}
int main(){
int num;
printf("请输入一个数#");
scanf("%d",&num);
MulTable(num);
system("pause");
return 0;
}
打印一个数的每一位
递归方式实现打印一个整数的每一位
#include <stdio.h>
#include <Windows.h>
#include <math.h>
#pragma warning(disable:4996)
void PrintPlace(int num){
if (num > 9){
PrintPlace(num / 10);
}
printf("%d\n", num % 10);
}
int main(){
int num;
printf("请输入一个数#");
scanf("%d",&num);
PrintPlace(num);
system("pause");
return 0;
}
求阶乘
递归和非递归分别实现求n的阶乘(不考虑溢出的问题)
#include <stdio.h>
#include <Windows.h>
#include <math.h>
#pragma warning(disable:4996)
int Factorial(int num){
int result=1;
if (num == 0){
return result;
}
else{
for (int i = num; i >= 1; i--){
result *= i;
}
}
return result;
}
int RecFact(int num){
int result = 1;
if (num == 0){
return result;
}
else
return num*RecFact(num - 1);
}
int main(){
int num;
printf("请输入一个数#");
scanf("%d",&num);
printf("%d\n", Factorial(num));
printf("%d\n", RecFact(num));
system("pause");
return 0;
}
strlen的模拟
递归和非递归分别实现strlen
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:4996)
int MyStrlen(char* str){
int ret = 0;
while (*str != '\0'){
ret++;
str++;
}
return ret;
}
int MyStrlenRec(char* str){
if (*str != '\0'){
return 1+MyStrlenRec(str + 1);
}
else{
return 0;
}
}
int main(){
char arr[] = "just a test";
printf("%d\n", strlen(arr));
printf("%d\n", MyStrlen(arr));
printf("%d\n", MyStrlenRec(arr));
system("pause");
return 0;
}
字符串逆序(递归实现)
编写一个函数 reverse_string(char * string)(递归实现)
实现:将参数字符串中的字符反向排列。
要求:不能使用C函数库中的字符串操作函数。
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:4996)
void reverse_string(char * string){
if (*string != '\0'){
reverse_string(string+1);
printf("%c", *string);
}
}
int main(){
char arr[] = "just a test";
printf("%s\n",arr);
reverse_string(arr);
printf("\n");
system("pause");
return 0;
}
计算一个数的每位之和(递归实现)
写一个递归函数DigitSum(n),输入一个非负整数,返回组成它的数字之和
例如,调用DigitSum(1729),则应该返回1+7+2+9,它的和是19
输入:1729,输出:19
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:4996)
int DigitSum(int n){
if (n > 9){
return (n % 10) + DigitSum(n / 10);
}
else{
return n;
}
}
int main(){
int n = 1729;
n = DigitSum(n);
printf("%d\n", n);
system("pause");
return 0;
}
递归实现n的k次方
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:4996)
int Power(int n, int k){
if (k > 0){
return n*power(n, k - 1);
}
else{
return 1;
}
}
int main(){
int n,k;
printf("请输入n和k# ");
scanf("%d %d", &n, &k);
printf("%d\n", power(n, k));
system("pause");
return 0;
}
计算斐波那契数
递归和非递归分别实现求第n个斐波那契数
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:4996)
int Fibonacci(int n){
int num1 = 1;
int num2 = 1;
int tmp = 0;
if ((n == 1) || (n == 2)){
return 1;
}
if (n > 2){
for (int i = 3; i <= n; i++){
tmp = num2;
num2 += num1;
num1 = tmp;
tmp = 0;
}
}
return num2;
}
int FibonacciRec(int n){
int num1 = 1;
int num2 = 1;
int tmp = 0;
if ((n == 1) || (n == 2)){
return 1;
}
if (n > 2){
return FibonacciRec(n - 1)+FibonacciRec(n - 2);
}
}
int main(){
int n;
printf("请输入n# ");
scanf("%d", &n);
printf("%d\n", Fibonacci(n));
printf("%d\n", FibonacciRec(n));
system("pause");
return 0;
}
问题与解决
近期学习到了一些有效的debug技巧,罗列如下:
1、定位bug的最核心思路是“二分查找”。
2、查看中间结果通常有以下两种方法:
(a)打印日志(printf)(较为麻烦)
(b)借助调试器(较为方便)
使用VS调试器,必须保证处于Debug模式。
Debug是编译生成调试版本的可执行程序。
Release是编译生成发布版本的可执行程序。
调试器的核心操作:
(1)设置断点
(2)调试执行