C语言学习2.0
1、C语言的语句语法
三种基本结构:分支结构、循环结构、顺序结构
1、条件分支结构:
if(){ . . . } else if { . . .} else { . . .}
注:1、在if 后面加入一个,条件判断结束;
2、if 条件判断内容易将 == 写成赋值 = ;
3、if 条件判断执行的命令只有一行;
多条件分支结构:switch case ;switch判断的是整型;
switch (a)
{
case 1:
case 2:
case 3:
//......
default:
}
switch (a)
{
case 1:
printf("is 1!\n");
case 2:
printf("is 2!\n");break;
case 3:
printf("is 3!\n");break;
default:
break;
}//输出结果is 1! is 2!
注:1、char 和 int 都是可以进行 swtich 条件判断
2、每一个分支结构(case) 后面要接 break 否则会一直执行后面的case直到跳出
2、循环结构
for(初始条件;循环条件判断;循环步长)
{
}
#include<stdio.h>
int main()
{
for(int i=1;i<=100;i++)
if(i % 2 == 0)
printf("%d",i);
printf("hello");
return 0;
}
注:1、for 循环作用范围只有一条命令
2、如果for 没有条件判断就会进入死循环,此时用break(作用:直接跳出当前结构)跳出循环;
while()//循环条件判断
{
//......
}
int i=100;
while(i)
{
printf("%d",i);
i--;
}
return 0;
do
{
//执行内容
}while(条件判断);
注:do{ …} while (); 无论满不满足循环条件都会执行一遍内容
int i=0;
for(;i;)
{
printf("for output!\n");
}
while(i)
{
printf("while output!\n");
}
do
{
printf("output!\n");
}while(i);
return 0;
//输出结果为:output!只有do while执行了一次
C语言随堂测试题:
#include<stdio.h>
#include<math.h>
//输出100-999内所有水仙花数
int main()
{
int i,units,hundreds,decade;
for(i=100;i<999;i++)
{
units=i/100;
decade=i / 10 %10;
hundreds=i%10;
if((pow(units,3)+pow(hundreds,3)+pow(decade,3))==i){
printf("%d is narcissus number",i);
}
}
return 0;
}
#include<stdio.h>
int main()
{
for(int row=1;row<=9;row++){
for(int column=1;column<=row;column++){
printf("%d * %d = %d",column,row,cloumn*row);
}
printf("\n");
}
return 0;
}//输出99乘法表
3、顺序结构
按照事物本身的特性,必须一个接一个来完成。
2、函数
1、简单介绍
作用:封装重复使用的代码
//返回值 函数名(传递的参数)
//{
// 函数主体
// return 和返回值类型相同的数
//}
//验证一个数是否满足哥德巴赫猜想:任意>6的数可以拆分成两个素数的和
#include<stdio.h>
#include<math.h>
//判断一个数是否为素数
int IsPrime(int a)
{
if(a==2) return 1;
for(i=2;i<= sqrt(a);i++)
{
if(a%i==0)
return 0;
}
return 1;
}
int main()
{
int a;
scanf("%d",&a);
if(a<6)//输出一个大于6的数
{
printf("Error Input!Please input again!");
return 0;
}
for(int i=1;i<a/2;i++)
{
if(IsPrime(i)==1 && IsPrime(a-i)==1)//判断是否存在两个素数
{
printf("first: %d\n second : %d\n",i,a-i);
}
}
return 0;
}
2、宏:
define的应用:
1、宏定义:方便替换
#include<stdio.h>
#define ABS(x) (x)<0? (-x):(x)
#define Max(a,b) (a>b)? (a):(b)
#define M(x,y,z) x*y+z
int main()
{
printf("|-1| = %d\n",ABS(-1));
printf("Max : %d\n",Max(3,2));
int a=1,b=2,c=3;
printf("%d\n",M(a+b,b+c,a+c));
return 0;
}//运行结果为:|-1| = 1 Max : 3 12
//最后一个算式在替换的时候由我们想得到的(a+b)*(b+c)+(a+c)变成了a+a*b+c+a+c所以得出的结果不理想
2、条件编译:方便跨平台开发
//1.h 定义一些宏加一些宏判断,防止头文件重复包含
#ifndef __MYHEAD_H__
#define __MYHEAD_H__
#define LOG(log) Write_log(__FILE__,__func__,__LINE__,log)
void Write_log(char *FileName,char *FuncName,int line,char *log);
#endif
3、宏函数
用宏定义的函数,在某些时候能够代替函数的作用;函数式宏能是程序的运行速度稍微提高一点儿,但是当函数中有大量的宏替换的时候,又会使得程序变得臃肿。
#include<stdio.h>
#include"1.h"
void Write_log(char *FileName,char *FuncName,int line,char *log)
{
printf("FileName:%s\n FuncName:%s\n Line:%d\n Log:%s\n",FileName,FuncName,line,log);//输出文件名,函数名,当前行数,日志
}
#include<stdio.h>
#include"1.h"
#include<math.h>
int main()
{
// printf("%s\n",__FILE__);//所在文件名
// printf("%s\n",__DATE__);//日期
// printf("%s\n",__TIME__);//时间
// printf("%d\n",__LINE__);//行数
// printf("%s\n",__func__);//所在函数名称
Write_log(__FILE__,__func__,__LINE__,"this is a test log");
int i=0;int j=0;
LOG("Zero error!\n");
}
单目运算符:a++ b–
双目运算符:+ - * / %
三目运算符:?‘yes’:‘No’
注:1、宏定义只做替换不做计算,所以在用宏函数的时候不加注意就会发生错误;
2、在定义函数式宏的时候与一定要每个参数以及整个表达式都用()括起来;
3、一行定义不下,\表示下一行还有内容
ps:x & (x-1)
能够将二进制中的1全部变成0
3、回调函数
回调函数是一种特殊的函数,它把函数作为参数传递给另一个函数,并在被调用函数执行完毕后被调用。
决定什么时候调用这个函数,但是并不会关心这个函数具体干什么,降低函数与被调函数之间的关联,把部分函数功能交由别的函数去完成,使函数与部分功能取消强绑定。
回调函数的作用是将代码逻辑分离出来,使得代码更加模块化和可维护。使用回调函数可以避免阻塞程序的运行,提高程序的性能和效率。另外,回调函数还可以实现代码的复用,因为它们可以被多个地方调用。
优点:
- 提高代码的复用性和灵活性:回调函数可以将一个函数作为参数传递给另一个函数,从而实现模块化编程,提高代码的复用性和灵活性。
- 解耦合:回调函数可以将不同模块之间的关系解耦,使得代码更易于维护和扩展。
- 可以异步执行:回调函数可以在异步操作完成后被执行,这样避免了阻塞线程,提高应用程序的效率。
回调函数的应用:
#include<stdio.h>
int add(int a,int b)
{
return a+b;
}
void Write_to_Client1(const char *str)//发送给客户1
{
printf("write to Client1 info: %s\n",str);
}
void Write_to_Client2(const char *str)//发送给客户2
{
printf("write to Client2 info: %s\n",str);
}
void Func(void (*ptr)(const char *str))//传递发送字符,不指定送给谁
{
const char *s ="hello";
ptr(s);
}
int main()
{
Func(Write_to_Client1);//在main中指定送给谁
Func(Write_to_Client2);
return 0;
}
4、应用一:交换两个变量的值
方法一、多变量
int swap(int a,int b)
{
int c;
c=a;
a=b;
b=c;
printf("a : %d\n b : %d\n",a,b);
}
方法二、数轴法
int swap2(int a,int b)
{
a=a-b;
b=a+b;
a=b-a;
printf("a1 : %d\n b1 : %d\n",a,b);
}
但是数据在相减的时候在超出一定范围之后,无法保证两个数依然有效
方法三、位操作:所有的数在计算机里存储都是补码形式存在,所以~-1=0;-1原码为:1000 0001 补码为1111 1111 对其取反为0000 0000 所以对-1取反为0;对0取反为-1;
取反操作和去反码操作不同点:看是否带符号位运算,带符号位的为取反操作,不带符号位的为去反码操作。
char a[10] = {0};
while(~scanf("%s",&a))//代表正常输出能够进入循环,非法操作后跳出循环
{
printf("%s\n",a)
}
^异或:相异为1,相同为0;
int swap3(int a,int b)
{
a=a^b;
b=a^b;
a=a^b;
printf("a2 : %d b2 : %d\n",a,b);
}
3、头文件重定义问题
在main.c 引用多个头文件时可能出现重定义的问题,如在main.c 中引用1.h和2.h 时,如果2.h中包含1.h,则main.c在预处理展开的时候会出现包含两个1.h的内容会出现重定义的问题;为了避免这种问题则在头文件中加入条件判断,这个判断要在每一个头文件中都要写;
//头文件预判断
#ifndef __MYHEAD_H__
#define __MYHEAD_H__
//真正头文件内容
#endif
4、指针
#include<stdio.h>
int swap(int a,int b)
{
int c;
c=a;
a=b;
b=c;
printf("a : %d\n b : %d\n",a,b);
}
int main()
{
int a=2,b=3;
swap(a,b);
printf("a1 : %d\n b1 : %d\n",a,b);
return 0;
}//此时输出结果为 a:3 b:2 a1:2 b1:3主函数体的数值并没有发生改变
函数体调用并不会影响主函数的取值,所以引入指针指向数据的地址从而实现数据的互换。
指针 &:取地址 *:取值符号
#include<stdio.h>
int swap(int *a,int *b)
{
int c;
c = *a;
*a = *b;
*b = c;
printf("*a : %d\n *b : %d\n",*a,*b);
}
int main()
{
int a=2,b=3;
int *ptr_a = &a;
int *ptr_b = &b;
swap(ptr_a,ptr_b);
printf("a : %d\n b: %d\n",a,b);
return 0;
}//输出结果为:*a=3,*b=2;a=3,b=2;因为函数传递的是值,指针传递的是地址
const int * ptr_a=&a;//变量指向的值不能修改 常量指针
int * const p=&a;//变量指向的地址不能修改 指针常量
2、函数指针
函数指针的定义方式:函数返回值类型(*指针变量名)(函数参数列表)
void (*p)(int,int);
指向这一类void ()(int ,int)
的函数指针
#include<stdio.h>
int add(int a,int b)
{
return a+b;
}
int sub(int a,int b)
{
return a-b;
}
int main()
{
int (*p)(int,int)=NULL;
p=add;
printf("%d\n",p(1,2));
p=sub;
printf("%d\n",p(1,2));
return 0;
}
封装:将数据或函数等集合在一个个的单元中(我们称之为类)。被封装的对象通常被称为抽象数据类型。封装解决了函数重名等问题;
优点:避免了函数体重名问题;可以由结构体成员直接调用对象.实现函数体的调用
缺点:1、极大地增加了结构体的内存损耗;
注意:指针函数:返回值为指针的函数 int * p(int,int)
函数指针函数:把函数指针作为返回值的函数
//函数指针函数使用场景:实现公式解析
#include<stdio.h>
typedef int (*Mathfunc)(int,int);
int add(int a,int b)
{
return a+b;
}
int sub(int a,int b)
{
return a-b;
}
Mathfunc GetMathFunc(char Symbol)
{
switch(Symbol)
{
case '+': return add;
case '-': return sub;
default:
return NULL;
}
}
int main()
{
char Symbol = '-';
Mathfunc func = GetMathFunc(Symbol);
if(func!=NULL)
printf("%d\n",func(1,2));
return 0;
}
5、数组
1、数组的定义:
一组相同数据类型的数据元素的集合,地址是连续的;int a[10]
注意:数组的大小一开始就要确定,不能使用变量;int b=5;int a[b]={0};
非法输入
2、数组的初始化:
int a[]={1,2,3,4,5,6,7};int b[10]={1,2,3,4,5,6,7,8,9};
数组作为函数参数被传递的时候会被弱化成指针,无法知道具体大小,所以同时要加入数组的长度;但是字符数组除外,它作为字符串传入时,以’\0’结束。
#include<stdio.h>
#define ArrayLen(a) (sizeof(a))/sizeof(a[0])
void print(int *a,int len)
{
for(int i=0;i<len;i++)
{
printf("%d ",a[i]);
}
printf("\n");
}
int main()
{
int a[]={1,2,3,4,5,6,7,8};
print(a,Arraylen(a));
return 0;
}
&a : 整个数组的首地址,一个步长跨整个数组
&a[0]:数组首元素的地址,一个步长跨一个元素的长度
a:数组首元素的起始地址,一个步长跨一个元素的长度
#include<stdio.h>
int main()
{
int a[4]={1,2,3,4};
printf("%x ; %x ; %x \n",&a,&a[0],a);
printf("%x ; %x ; %x \n",&a+1,&a[0]+1,a+1);
return 0;
}//输出结果为
//65d025b0 ; 65d025b0 ; 65d025b0
//65d025c0 ; 65d025b4 ; 65d025b4
int a[4]={2016,2017,2018,2019};
int *ptr1=(int*)(&a+1);
int *ptr2=(int*)((int)a+1);
printf("%x,%x",ptr1[-1],*ptr2);
//输出ptr1[-1]为整个数组最后一个元素的起始地址,*ptr2为首元素内地址+1
#include<stdio.h>
int main()
{
char arr[]={'a','b','c','d'};
printf("%d\n",sizeof(arr));//输出整个数组大小:4个字节
printf("%d\n",*(arr+0));//arr表示数组首元素地址,对其跨0个元素大小再取值,输出为'b'的大小:98
printf("%d\n",sizeof(*arr));//输出为'a'的大小:1个字节
printf("%d\n",sizeof(arr[1]));//输出第一个元素的大小:1个字节
printf("%d\n",sizeof(&arr));//输出为整个数组首地址的大小,为8个字节
printf("%d\n",sizeof(&arr+1));//输出为首个元素跨一个元素的地址的大小:8个字节
printf("%d\n",sizeof(&arr[0]+1));//输出为首个元素跨一个元素的地址的大小:8个字节
}
- 课堂测试一:
用数组输出2^10000超大整型数:模拟计算机计算的过程,将数组每一位看作位数,当超过10的时候向前进一位,前面一位+1,自己减10;直至结束;
#include<stdio.h>
int main()
{
int a[100]={0};
a[0]=1;
for(int i=0;i<10000;i++)
{
for(int j=0;j<100;j++)
{
a[j]=a[j]*2;
}
for(int j=0;j<99;j++)
{
if(a[j]>=10)
{
a[j+1]=a[j+1]+1;
a[j]=a[j]-10;
}
}
}
int flag=0;
for(int i=99;i>=0;i--)
{
if(a[i]!=0)
{
flag =1;
}
if(flag==1)
{
printf("%d",a[i]);
}
}
printf("\n");
return 0;
}
课后习题:123456789 * 987654321 = ?
-
课堂测试二:
输出斐波那契数列:1 1 2 3 5 8 13 21
#include<stdio.h>
int main()
{
int i;
long a[50]={0};
a[0]=1;a[1]=1;
for(i=1;i<49;i++)
{
a[i+1]=a[i]+a[i-1];
}
for(i=0;i<50;i++)
{
printf("%ld\t",a[i]);
}
return 0;
}
**2、字符数组:**用来存放字符的数组
1、定义:
字符数组实际上是一系列字符的集合,也就是字符串(String)。在 C 语言中,没有专门的字符串变量,没有 string 类型,通常就用一个字符数组来存放一个字符串。在 C 语言中,字符串总是以’\0’作为结尾,所以’\0’也被称为字符串结束标志,或者字符串结束符。
2、字符数组的初始化:
-
通过字符数组直接初始化 (直接把数组元素赋值给数组名是不行的)
char str[10]="China";
错误表达:
char str[10]; str="string";
-
使用strcpy函数进行初始化
char str1[10]; str2[]="string";strcpy(str1,str2);
-
通过指针赋值
char *string="I love China";
3、string.h头文件所包含的常见的函数:
(1)、strlen:输出字符串的长度;
sizeof
strlen
的区别:
sizeof
记录的是数组所占内存大小包括’\0’ ; sizeof
是一个单目运算符,它的参数可以是数组、指针、类型、对象、函数等。
strlen
记录的是字符串的长度;是函数
//自定义函数实现strlen的功能
int MyStrlen(char *s)
{
int count=0;
while(*s != '\0')
{
count++;
s++;
}
return count;
}
(2)、strcmp(a,b)
:比较字符串是否相同,相同返回0,不同返回1
//自定义函数实现strcmp的功能
int MyStrCmp(char *s1,char *s2)
{
if(MyStrlen(s1)!=MyStrlen(s2))//字符串的长度不相等直接说明两个字符串不同
return 0;
while(*s1!='\0' && *s2!='\0')
{
if(*s1!=*s2)//判断字符串的值是否相等
{
return 0;
}
s1++;
s2++;
}
return 1;
}
(3)、strncmp(a,b,n):
比较字符串前n个字符是否相同
(4)、strcpy(目标字符串,要被拷贝的字符串):
复制拷贝字符串
//自定义函数实现strcpy的功能
void MyStrCpy(char *dest,char *src)
{
if(MyStrlen(src)==0)//判断字符串长度是否为0,为0则无法赋值
printf("string is NULL!error copy!\n");
while(*src!='\0')
{
*dest = *src;
dest++;
src++;
}
*dest = '\0';
}
(5)、strncpy(目标字符串,要被拷贝的字符串,n)
:复制拷贝前n个字符
(6)、strcat(a,b)
:字符串的拼接,将后面的字符串拼接到前面一个字符串
(7)、strncat(a,b,n)
:n代表要拼接的长度,拼接n个字节长度的字符串
(8)、strstr(a,b)
:字符串匹配;返回值为指针,返回匹配成功的字符在原字符串中的地址
-
课堂测试三:
实现倒序输出字符串eg:
i am from nanjing------>nanjing from am i
//思想:先将整条语句倒叙--->gnijnan morf ma i,再将每个单词倒叙
#include<stdio.h>
#include<string.h>
//倒序输出整句话
void Exchange(char *s,int len)
{
char temp;
for(int i=0;i<len/2;i++)
{
temp = s[i];
s[i] = s[len-i-1];
s[len-i-1] = temp;
}
}
//倒序输出单词
void Exchange2(char *s)
{
int len=0;
for(int i=0;i<strlen(s);i++)
{
if(*(s+i)== ' '||*(s+i)=='\0')
{
Exchange((s+i-len),len);
len=0;
}
else{
len++;
}
}
}
int main()
{
int a[]="I am from NanJing";
Exchange(a,strlen(a));
printf("%s\n",a);
Exchange2(s);
printf("%s\n",s);
return 0;
}
-
课堂测试四:
将字符型转换成整数型,不能使用其他函数
#include<stdio.h>
int atoi(char *s)
{
int result =0;
int sign=1;
while(*s!='\0')//判断字符串是否结束
{
if(*s=='-')
sign = -1;//符号位为负数
if(*s<='9' && *s>='0')//判断字符串元素是否处于9~0
{
result = result*10 +*s -'0';
}
s++;
}
return result*sign;
}
int main()
{
char s[]="100";
int a=atoi(s);
printf("%d\n",a);
return 0;
}
实现封装string里面的函数,创建一个结构体要先对其进行初始化
//MyString.h实现初始化和打印输出
#ifndef __MYSTRING_H_
#define __MYSTRING_H_
#define Max 1024
typedef struct String
{
char string[Max];
int size;
}MyString;
void Initalize(MyString *obj,const char *str);//不允许指针修改文字常量区的内容
void Print(MyString *obj);
int IsEqual(MyString *obj1,MyString *obj2);
int IsContains(MyString *dest,MyString *src);
void RemoveString(MyString *obj,const char *str);
void InsertString(MyString *dest,const char *str,int index);
#endif
//MyString.c
#include"MyString.h"
#include<string.h>
#include<stdio.h>
void Initialize(MyString *obj,const char *str)
{
strcpy(obj->string,str);
obj->size=strlen(str);
}
void Print(MyString *obj)
{
printf("%s\n",obj->string);
}
int IsEqual(MyString *obj1,MyString *obj2)
{
if(strcpm(obj1->string,obj2->string)==o)
return 1;
else
return 0;
}
int IsContains(MyString *dest,MyString *src)
{
char *str=strstr(dest->string,src->string);
if(str == NULL)
{
return 0;
}
else
{
return 1;
}
}
void RemoveString(MyString *obj,const char *str)
{
char *RMstr = strstr(dest->string,str);
if(RMstr == NULL)
{
return;
}
else
{
char *destination = RMstr +strlen(str);//RMstr为指针,指向被查找字符串内匹配到的字符串的起始位置,向右移匹配到的字符串的长度
while(*destination!='\0')
{
*RMstr = *destination;
RMstr++;
destination++;
}
*RMstr = '\0';
}
}
void InsertString(MyString *dest,const char *str,int index)
{
if(index < 0 ||index > dest->size)
return 0;
char new_str[Max]={0};
strncpy(new_str,dest->string,index);
strncpy(new_str+index,str,strlen(str));
strcpy(new_str+index+strlen(str),dest->string+index);
strcpy(dest->string,new_str);
dest->size=strlen(dest->string);
}
//main.c
#include<stdio.h>
#include"MyString.h"
int main()
{
MyString obj;
Initialize(&obj,"helloworld");
Print(&obj);
MyString obj1;
Initialize(&obj1,"helloword");
MyString obj2;
Initialize(&obj2,"owor");
if(IsEqual(&obj,&obj1)==1)
{
printf("Two strings are the same!\n");
}
else
{
printf("Two strings are different!\n");
}
if(IsContains(&obj1,&obj2)==1)
{
printf("Is cantains!\n");
}
else
{
printf("Is not cantains!\n");
}
InsertString(obj1,"DEFINE",1);
Print(&obj);
return 0;
}
对其进行修改:将函数封装进结构体,可以实现直接使用 变量名.函数名
实现对函数的调用,但是初始化函数不能封装,而且初始化函数要对这些函数进行初始化。
//Mystring.h做出的修改
#ifndef __MYSTRING_H_//条件编译,避免头文件重复包含
#define __MYSTRING_H_
#define Max 1024
#define Init_MyString(obj,str) MyString obj;Initialize(&obj,str)
typedef struct String MyString;
struct String
{
char string[Max];
int size;//数组长度,用空间换时间,用一块内存存储字符串的长度,方便下一次调用
void (*print)(MyString *obj);//输出打印字符串
int (*isEqual)(MyString *obj1,MyString *obj2);//判断字符串是否相等
int (*isContains)(MyString *dest,MyString *scr);//判断是否包含
int (*StrintSize)(MyString *obj);//判断字符串的大小
void (*removeString)(MyString *dest,const char *str);//删除指定字符串
void (*insertString)(MyString *dest,const char *str,int index);//在指定位置插入字符串
};
void Initialize(MyString *obj,const char *str);
#endif
//在Mystring.c中做出的修改
#include "MyString.h"
#include<string.h>
#include<stdio.h>
//前置声明
void Print(MyString *obj);
int IsEqual(MyString *obj1,MyString *obj2);
int IsContains(MyString *dest,MyString *src);
int Size(MyString *obj);
void RemoveString(MyString *dest,const char *str);
void InsertString(MyString *dest,const char *str,int index);
//对结构体定义的变量和函数指针进行初始化
void Initialize(MyString *obj, const char *str)
{
strcpy(obj->string,str);
obj->size = strlen(str);
//func Init
obj->print = Print;
obj->isContains = IsContains;
obj->isEqual =IsEqual;
obj->StrintSize = Size;
obj->insertString = InsertString;
obj->removeString = RemoveString;
}