重生之我学C语言

C语言程序设计

一、程序设计与C语言

程序,是用特殊编程语言写出来指导计算机如何解决问题的,即描述要求它如何去做的过程或方法,告诉计算机怎么一步一步去做(how to do)。

选择一种语言,主要是其所带的库和传统所决定的。


二、数据存储与运算

2.1 标识符const 

标识符的定义规则:

  • 不能以数字开头
  • 不能是关键字

如_HJ是标识符,但9_s、long均不是

const int amount=100

const表示只读,因此常量amount不可修改不可赋值

2.2 输入输出

 当scanf出错时,变量的值没有发生变化

2.3 浮点数

两个整数运算的结果只能是整数,2与2.0意义不一样,2.0表示浮点数(带小数点的数)

当整数与浮点数运算时,c会将整数转化为浮点数再计算

 例题2.1:计算时间差

scanf("%d %d",&h1,&min1);
scanf("%d %d",&h2,&min2);
int t1=h1*60+min1;//将时间转化为整数
int t2=h2*60+min2;
int t=t2-t1;//t表示时间差有多少分钟
printf("时间差是%d小时%d分钟",t/60,t%60);
// t/60两个整数运算,结果四舍五入取整数——>小时,
// t%60,取模运算,计算余数——>分钟

例题2.2:两个整数计算平均数

double c=(a+b)/2.0;//2.0这样得到的平均数包含小数部分

三、判断

3.1 if-else语句

if(1){
    ……
}//满足1的表达式执行
else if(2){
    ……
}//不满足1但满足2执行
else if(3){
    ……
}//不满足1和2但满足3执行
else{
    ……
}//123都不满足,执行

3.2 switch-case语句

switch语句可以看作是基于计算的跳转,计算表达式后跳转到相匹配的case处,执行后面的代码,直到遇到break才跳出switch,或者switch结束。

switch(a){//a只能是整型的结果
    case *://*可以是常数,也可以是常数计算的表达式
    ……
    break;
    default:……
}

四、循环

4.1 while 与do-while 

do-while 与while一个是先执行再判断条件,一个是先判断条件再执行循环体。do-while进入循环的时候不做检查,先执行一轮循环体代码后再检查条件是否满足。

例题2.3:猜随机数

①生成100以内的随机数

#include<stdlib.h>//此库包含随机数函数 
#include<time.h>//让每次运行的代码生成的随机数不一样 

    srand(time(0));
    int number=rand()%100+1;//rand()%100得到的是0~99的数 

#include<stdio.h>
#include<stdlib.h>//此库包含随机数函数 
#include<time.h>//让每次运行的代码生成的随机数不一样 
int main(){
	srand(time(0));
	int number=rand()%100+1;//rand()%100得到的是0~99的数 
	int count=0;
	int a=0;
	printf("我已经想好一个1~100的整数了\n");
	printf("请猜这个数是多少:");
	do{
		scanf("%d",&a);
		count++;//猜的次数加一
		if(a>number)
			printf("大了哦,再猜:");
		else if(a<number) 
			printf("小了哦,再猜:"); 
		else
			printf("恭喜你猜对了,是%d",a);
	}while(a!=number);
	return 0; 
} 

例题2.4:算平均数

用do-while循环,number!=-1判断了两次,while循环优于do-while

//sum求和(作被除数),count计数输入了多少个数(作除数),number保存每次输入的数
//用while循环
scanf("%d",&number);
while(number!=-1){
	count++;
	sum+=number;
	scanf("%d",&number);
	} 
printf("平均数是%f",sum/(double)count);
/*用do-while循环 
do{
	scanf("%d",&number);
	if(number!=-1){
		count++; 
		sum+=number;
	}
}while(number!=-1);*/ 

例题2.5:整数逆序

取出每一位的数字:

②<—依次分解整数

1 对一个整数%10得到其个位数
2 对一个整数/10丢掉他的个位数
3 对2的结果%10得到他的十位数
4 对2的结果/10丢掉他的个位数.... 

//从低位到高位依次求解 
while(x>0){
    digit=x%10;//每轮循环得到最后一位的数字用digit存储
    //若要求100输出为001
    //	printf("%d",digit);//依次输出每一位即可实现逆序输出
    //若要求100输出为1
    ret=ret*10+digit;//相当于在已经逆序完的几位数后加一个最新需要逆序的数,比如说123,已经逆序完成32,最新需要逆序的是1,即32后添加0再加上1(32*10+1=321)
	x/=10; //去掉最后一位 		
	} 
	printf("%d",ret);

4.2 for

for(int i=1;i<=n;i++)//i 为局部变量
for(i=n;i>1;i--)//i从n一直循环到1
for(n=n;n>1;n--)//n从n一直循环到1
for(;n>1;n--)//省略n=n

任何一个for循环都可以用while循环表示出来

tip for loops

  1. 固定次数用for
  2. 必须执行一次用do-while
  3. 其他情况用while 

4.3 break与continue

例题2.6:判断素数

③ 枚举法:x能被i整除

        x%i==0,可以用来判别是不是素数,求最大公约数等

break跳出整个循环体

还可以设置一个布尔变量(初始设置为true),不是素数是将变量值设置为false。

for(int i=2;i<x;i++){
	if(x%i==0){//即x能被除1和本身整除,x不是素数
		printf("%d不是素数",x); 
		break; 
	}

continue:这一轮continue之后未执行的语句不再执行,转而执行下一轮循环语句

举个例子:音乐课上让一排的同学依次完整地唱一首歌,第一个同学唱完之后,第二个同学唱的时候忘词没唱完(continue),后面的同学需要接着唱,但第三个同学唱到一半的时候下课了,此时唱歌结束(break)。

4.4 嵌套循环的退出

4.4.1 接力break

break只能跳出当前一层的循环,若当嵌套循环最内层循环break结束时要求整个循环也结束,需要连续使用break,但后续外层循环不能简单地break,需要判断最内侧break是否执行。

例题2.7:凑硬币

用1角、2角、五角的硬币凑10元以下的钱,只要求有一种组合就可以。

int x;
int one,two,five;
bool exit=false;
scanf("%d",&x);
for(one=1;one<x*10;one++){
	for(two=1;two<x*10/2;two++){
		for(five=1;five<x*10/5;five++){
			if(one*1+two*2+five*5==x*10){
				printf("%d枚1角%d枚2角%d枚5角能凑出%d元\n",one,two,five,x);
				exit=true; 
				break;
			} 
		}//第一个break退出此循环,此时不能直接break,要判断第一个break是否执行
		if(exit) break; 
	}
	if(exit) break; 
}
4.4.2 goto

在多层嵌套循环内层跳到最外层时非常适合goto,goto **会跳到**的位置执行后面的代码

for(;;){
	for(;;){
		for(;;){
			if(){
				goto out; 
	        }
        }
   }
}
out://goto跳到此处执行后面的代码
…………

例题2.8 求前n项和

1/2+1/3+1/4+……1/n

for(int i=1;i<=n;i++){
    sum+=1.0/i;
}

1/2-1/3+1/4-……1/n

int sign=1;
for(int i=1;i<=n;i++){
    sum+=sign*1.0/i;
    sign=-sgin;
}

例题2.9 整数分解

12345—>1 2 3 4 5

一次逆序再一次逆序输出,这种方式遇到200时,结果只有2

//将数165逆序得到数561
do{
    int d=x%10;
    set=set*10+d;
    x/=10;
}while(x>0);
x=t;
//再从右往左输出最后一位,得到1 6 5
do{
    printf("%d ",x%10);
    x/=10;
}

改进:

④—>依次分解整数

1 while循环求mask(比如x=1234,mask=1000)

1  x/mask得到最左边一位,x%mask去掉最左边一位

2  mask/=10,重复2

#include<stdio.h>
int main(){
	int x,d,mask=1,t;
	scanf("%d",&x);
	t=x;
	//假设x为n位,得到mask=10^(n-1); 
	while(t/10>0){//t>9也行 
		mask*=10;
		t/=10;  
	} 
	do{
		int d=x/mask;
		printf("%d",d);
		if(mask>0)
			printf(" ");
		x%=mask;
		mask/=10;
	}while(mask>0);
	return 0;
}

例题2.10 求最大公约数

枚举法:

1  i 从1到min{a,b}开始循环,每次循环结束+1

2  若a、b均能被i整除,公约数r=i,直至循环结束

for(int i=1;i<=a&&i<=b;i++){
	if(a%i==0&&b%i==0){
		r=i;
	}
}//最大公约数为r
⑤辗转相除法:

1 如果b=0,计算结束,a为最大公约数

2 否则 计算余数c=a%b,让a=b,b=余数c

3 回到第一步

a b c

6 9 6 

9 6 3

6 3 0

3 0

#include<stdio.h>
int main(){
	int a,b,c;
	scanf("%d %d",&a,&b);
	while(b){
		c=a%b;
		a=b;
		b=c;
	}
	printf("最大公约数是%d",a);
	return 0;
}

五、类型

5.1 类型分类

整数:

  • char,1字节,8比特,能表示2^{8}个整数,一半负数,另一半正数+0,即-128~127
  • short(2),-32768~32767,
  • int(取决于CPU)
  • long(4)
  • long long(8)

        unsigned(无符号)只是输出不同,内部计算一样的

浮点数:float(输入%f,输出%f、%e科学计数法)、double(输入%lf,输出%f、%e)、long double 

        inf:+∞,-inf:-∞;nan:不存在

        tips:带小数点字面量是double而非float,可以用3.145f来表明是float                                                       两个相等的浮点数比较可能会失败,可以用fab(f1-f2)<1e-12

字符类型: 

        char 还能表示字符,' '=32,'0'=48,'A'=65,'a'=97

逃逸字符:\b,\n,\t

逻辑:bool

指针

自定义类型

sizeof(int) :int 占多少字节(byte)

5.2 自动类型转换

对于printf任何小于int类型的会被转换成int,float会被转换成double

但scanf不会,要输入short需要%hd

5.3 强制类型转换

        一般是由大转小,优先级高于四则运算

5.4 逻辑运算(优先级)

 短路:表达式1&&表达式2,当左边表达式已经不成立,此时不会再执行右边的表达式,此时称右边的表达式被短路了。

条件运算符和逗号表达式

逗号是运算符,优先级最低,通常在for中使用

  • for(i=0,j=0;i<j;i++,j++)……

六、函数

6.1 函数定义、调用、声明

for(int i=0;i<number;i++){
		count[i]=0;
	}

当调用函数给的值与参数类型不匹配时,编译器会自动转换类型

c 语言在调用函数时,实参永远只能传值给形参,而并不是变量的传递

6.2 变量的生存期、作用域

对于本地变量其生存期和作用域在块内,即大括号内,本地变量只存在于块内的变量空间,当块执行结束,变量也随之消失


七、数组

编译器和运行环境不会检查数组的下标是否越界,a[0]存在但无用

7.1 定义及初始化

//数组定义初始化
const int number=10; //方便修改和代码阅读
int count[number]={0}; 
//数组的集成初始化
int a[]={1,2,3,4,5,6};

还可以 a[10]={[1]=2,4,[5]=6}; 表示给a[1]=2,a[2]=4,a[5]=6,其余全是0

7.2 数组的大小

 数组长度 length=sizeof(a)/sizeof(a[0]);

7.3 数组的赋值

数组变量本身不能被赋值,如int b[]=a;只能遍历,将每一个元素赋值给另一个数组

当数组作为函数参数时

  • 不能在[]中给出数组的大小
  • 不能再利用sizeof来计算数组的元素个数!

往往必须再用另一个参数来传入数组的大小

数组实例:素数

算法1:做加法,将素数一个一个写入数组

1.依次将每一个素数写入prime数组

2.在判断x是否为素数时,只需要看x能不能被x之前的素数整除,不能即为素数

/*判断x能不能被x前面的素数整除,
  a[]存储了x前面的素数,length表示数组的素数的个数*/ 
int IsPrime(int x,int a[],int length){
	int ret=1;
	for(int i=0;i<length;i++){
		if(x%a[i]==0){
			ret=0;
			break;
		}
	}
	return ret;
}

算法2:做减法,将不是素数的一个一个删除

构造素数表

1.开辟prime[n],初始化为1,表示假设从0~n-1每个数都是素数 
2.令x=2
3.如果x是素数,则将x~n内的所有x的倍数逻辑删除,即这些数都不是素数
      (i=2,x*i<n;i++) prime[i*x]=0; 

4.令x++,如果x<n,重复执行3,否则结束 

//遍历数组实现初始化之后
for(int x=2;x<n;x++){
	if(isprime[x]){
		for(int i=2;i*x<n;i++)
			isprime[i*x]=0; 
	}
}	

2.6.4 二维数组

int a[][5]={
    {0,1,2,3,4},
    {2,3,4,5,6},
};

列数必须给出,每行一个{},逗号分隔,最后一个逗号可以省略


八、指针

8.1 运算符&

        获得变量的地址,printf("%p",&i)

8.2 指针变量

指针:就是保存地址的变量

int i;   int *p=&i;//这里*是声明,*p即声明p是一个指针变量,p保存i的地址,p指向i的地址

后续的*可以理解为运算符,*p对p做运算找到p所指位置的内容,因此*p代表了i的值,对*p修改,就是通过*p找到i的位置对i的值修改

函数调用时,通过指针能保存带回的结果,因此传入参数就是需要保存带回的结果的变量

8.3 指针应用

场景一:调用swap函数,交换a、b的值

两种实现方式:

(1)直接交换a,b的地址,如

swap(int &pa,int &pb){
    交换pa和pb的值
}
swap(a,b);

(2)通过指针直接修改a,b的值

swap(int *pa,int *pb){
    交换*pa和*pb的值
}
swap(&a,&b);

场景二a:函数的返回值只有一个,因此当函数返回多个值,某些值只能通过指针返回

场景二b:函数返回运算的状态,结果通过指针返回

8.4 野指针

野指针是指向位置随机的、不正确的指针,系统无法对其进行操作,野指针指向的位置是随机的, 危害也是随机的,不一定会产生错误。

一种情况是,创建指针时没有对指针进行初始化,导致指针指向一个随机的位置,此时该指针就是一个野指针。后续我们对该指针进行操作时,比如说赋值,是将值写入该指针指向的那个随机位置,如果那个位置不能写,程序就会发生崩溃。

另一种情况是,释放指针指向的内存之后,指针没有置空,此时指针会指向“垃圾”内存

避免野指针

  1. 在创建指针时必须进行初始化;
  2. 在释放指针指向的内存之后必须将指针置空;

8.5 数组与指针

函数参数表里的数组其实是个指针,void f(int a[])与void f(int *a)等价

数组变量是特殊的指针,因为数组变量本身表达地址(就是a[0]的地址),因此

  • int a[10];int *p=a;//无需用&取地址
  • 但数组的单元表达的是变量,需要用&取地址
  • a==&a[0]
  • []可以对数组做也可以对指针做: p[0]<==>a[0]
  • *可以对指针做也可以对数组做:*a<==>a[0]
  • 数组变量是const的指针,因此不能被赋值:int a[] <==>int * const a=…
#include<stdio.h>
int main(){
	int a[10];
	int *p=a;
	printf("%d\n%d\n%d\n%d\n%d",a,&a[0],p[0],a[0],*a);
//运行结果a=&a[0],p[0]=a[0]=*a
}

8.6 指针与const

(1)指针是const        p是const              int *const p=&i;

            一旦得到了某个变量的地址,不能再指向其他变量:

  • 指针变量p是const,不能修改p的值,即指针p指向的位置是固定的

(2)所指是const        *p是const             const int *p=&i;

            表示不能通过这个指针去修改那个变量(并不能使那个变量成为const):

  • *p是const,无法通过*p=26去给i赋值为26
  • 但i并不是const,因此仍能通过i=26给i赋值
  • p也不是const,因此也可以修改p的值,让p指向其他变量

区分(1)、(2)通过判断const在*前面还是后面

转换:总是可以吧一个非const的值转换成const

            保护变量:void fun(const int *x){}//无法在fun函数f里对传入的变量进行修改

            保护数组值:int sum(const int a[]){}//无法在sum函数里对传入的数组进行修改

8.7 指针的运算

p-q:二者之间能存放多少个数,并不是二者的地址差

*P++:表示取出p所指位置的内容,再把p移到一下一位置,同理*++P,取出下一位置的内容

当不知道i是什么类型时,需要取出他的地址,可以用void *p=&i;

8.8 动态内存分配

int *p=malloc(number*sizeof(int));//动态申请一片空间(借)

free(p);//有借又还,free释放必须是malloc分配的初始地址

常见问题:

  • 申请了没free
  • free过了再free
  • 地址变了,直接free

九、字符串

char str={'a','b','c'}是一个字符数组,但不是一个字符串,char str={'a','b','c','\0'}是字符串

  • 字符串是以0结尾的一串字符
  • 0和'\0'一样,但与'0'不同
  • 0标志字符串的结束,但不是字符串的一部分,因此计算字符串长度时不包括这个0
  • 字符串以数组的形式存在,以数组或指针(更多以指针)的形式访问

9.1 字符串常量

“Hello”会被编译器变成字符数组,这个数组的长度时6,包含0,因为字符串是数组,因此不能直接通过运算符计算

9.2 字符串变量

字符串指针与数组的区别

指针:char *str="Hello World";

  • str是一个指针,初始化为指向一个字符串常量
  • 这个常量所在的地方是只读,实际上str 是const char* str
  • 不能对str所指的字符串修改

数组:char str[]="Hello World";

  • Hello World所在的地方仍是只读,但str[]相当于把Hello World拷贝到数组里
  • 此时,能对Hello World进行修改

构造字符串—>数组

处理字符串—>指针

char*并不是字符串,如果他所指的字符数组以\0结尾才是字符串

9.3 字符串输入输出

char string[8];

scanf(“%s”,string);

scanf读到空格、tab或者回车为止,但不安全,因为不知道要读入的内容长度,当超出string容量时程序可能崩溃

char word[8];

cahr word2[8];

printf(%s,word);

printf("##%s##",word2);

为什么输入12345678 12345678

输出是##12345678##?

https://a.hiphotos.baidu.com/album/pic/item/5882b2b7d0a20cf4c57fb01074094b36adaf9916.jpg?psign=c57fb01074094b36acaf2edda3cc7cd98c1001e93801bd2e

scanf("%7s",string);//告诉scanf函数最多允许读入7个字符,当读入7个字符后停止读入,不再依据空格、tab和回车

常见错误:

  • char *string;scanf("%s",string);//string未初始化,是一个野指针,可能指向不可修改的地址
  • char buffer[]=" ";//数组长度只有1,即'\0',不能放任何一个字符,应修改为char buffer[100]=" ";

9.4 字符串数组

char a[][10]={"LiMing","ZhangSan","LiSi"};

char *a[ ]={"LiMing","ZhangSan","LiSi"};

  • a[i]=%s:存储在 a[i] 指向内存位置的字符串内容
  • a[i]=%pa[i] 指针指向的内存地址
  • &a[i]=%pa[i] 指针本身的地址
  • *a[i]=%ca[i] 指向的第一个字符

字符串数组 a[][10]与*a[ ]的区别(图解):

9.5 getchar与putchar

  •  int putchar(int c);
  • 向标准输出写一个字符
  • 返回写了几个字符(1),EOF(-1)表示写失败

  • int getchar(void);
  • 从标准输入读入一个字符
  • 返回类型时int而不是char是为了返回EOF(-1)

问:为什么使用getchar()时,我们能键入多个字符?而不是输入一个字符之后就停止。

答:程序和键盘之间有一个中间平台shell窗口,当我们从键盘上输入时,其实是在向shell输入,然后shell再把我们的输入交给程序。当我们输入数据按下回车之前,我们的输入会一直停留在shell里,直到按下回车。在shell里有一个很大的缓冲区,当按下回车后,会将输入(包括回车)填入缓冲区,程序可以开始读入数据,此时若程序并未结束运行(比如说还需要数据),还可以继续输入数据到缓冲区,直到程序运行结束,或(CTRL+Z:在缓冲区键入EOF,强制结束程序)或(CTRL+D:键入-1,通过shell终止程序)

总的来说,getchar和scanf都是从shell的缓冲区里读数据,而用户输入是让shell将数据写入缓冲区

9.6 字符串函数

#include<string.h>

9.6.1 Strlen(串长)
  • size_t strlen(const char *s);
  • 返回s的字符串长度(不包括结尾的0)
9.6.2 Strcmp(比较大小)
  • int strcmp(const char*s1,const char *s2);
  • 比较两个字符串,返回0:s1==s2    1:s1>s2    -1:s1<s2(有的编译环境会返回差值)
9.6.3 Strcpy(拷贝)
  • char * strcpy(char *restrict dst, const char *restrict src);
  • 把src的字符串拷贝到dst
  • restrict表明src和dst不重叠(C99),返回dst

复制一个字符串:

char *dst=(char*)malloc(strlen(src)+1);//+1表示末尾的\0
stcpy(dst,src);
9.6.4 Strchr、Strrhr、Strstr、 Strcasestr(搜索)
  • char * strchr(const char *s, int c);//从左往右找到c第一次出现的位置
  • char * strrchr(const char *s, int c);//从右往左找到c第一次出现的位置

  • char * strstr(const char *s1, const char *s2);//字符串中找字符串
  • char * strcasestr(const char *s1, const char *s2);//忽略大小写

返回NULL表示没有找到

char s[] ="hello";

找到l第一次出现的位置,并把后续的字符串拷贝到t(输出llo)

tchar *p=strchr(s,'l');//p指向s从左往右第一次出现l的位置,p表示字符串'llo'

char *t=(char*)malloc(strlen(p)+1);
strcpy(t,p);

找到l第一次出现的位置,并把前续的字符串拷贝到t(输出he)

char c=*p;//用c暂存指针p指向的地址里的内容'l'
*p='\0';//将指针p指向的地址里的内容赋值为0,此时s[]所表示的字符串是"he"
char *t=(char*)malloc(strlen(s)+1);
strcpy(t,s);
*p=c;//把c里寄存的字符写回去

怎么找第二次出现的位置(输出lo)

p=strchr(p,'l');//p指向p所指向的字符串('llo')从左往右第一次出现l的位置,输出lo


十、枚举

枚举是一种用户定义的数据类型,用关键字enum来声明:

enum 枚举类型名字 {名字0,……,名字n};

大括号里的名字,它们是常量符号,类型为int,值依次从0到n。

        enum COLOR {RED,YELLOW,GREEN=5,NumCOLORS};

  • 声明枚举量的时候可以指定值,如GREEN=5
  • 当不指定值时,可以用NumCOLORS表达前面一共有多少个枚举量

十一、结构类型

十二、程序结构

十三、文件

13.1 格式化输入输出

printf:%[flags][width][.prec][hlL]type

printf("%d%n",a,&num);用num去记录输出a的长度

scanf:%[flage]type

返回值>0输入成功

13.2 文件输入输出

FILE *fp=fopen("file","w");

if(fp){

        fsacnf(fp,…);

        fprintf(fp,…);

        fclose(fp);

}else

        printf(“读取失败”);

十四、链表

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值