1.定义宏常量的两种方式
const int AMOUNT=100;
or
#define AMOUNT 100
2.浮点数
//float是单精度浮点数
//double是双精度浮点数
3.数据类型
//整数
int
printf("%d",...)
scanf("%d",...)
//带小数点的数
double
printf("%f",...)
scanf("%lf",...)
//浮点数输入必须要用%lf
4.复合赋值
total+=5;
equal
total=total+5;
//
total+=(sum+100)/2;
equal
total=total+(sum+100)/2;
//
total*=sum+12;
equal
total=total*(sum+12);
//
total/=12+6;
equal
total=total/(12+6);
++和–
//++和--可以放在变量的前面,叫做前缀形式,也可以放在变量的后面,叫做后缀形式。
//a++的值是a加1以前的值,而++a的值是加了1以后的值,无论哪个,a自己的值都加了1了。
#include
int main()
{
int a=10;
printf("a++=%d\n",a++);
printf("a=%d\n",a);
printf("++a=%d\n",++a);
printf("a=%d\n",a);
return 0;
}
//输出结果
/*
a++=10
a=11
++a=12
a=12
*/
表达式:count++ 运算:给count加1 表达式的值:count原来的值
表达式:++count 运算:给count加1 表达式的值:count+1以后的值
5.关系运算的结果
//当两个值得关系符合关系运算符的逾期时,关系运算的结果为整数1,否则为整数0
printf("%d\n",5==3);
printf("%d\n",5>3);
printf("%d\n",5<=3);
//输出结果
0
1
0
6.关系运算的优先级
所有的关系运算符的优先级比算术运算的低,但是 比赋值运算的高
//7>=3+4;
//int r=a>0;
#include
int main(){
printf("%d\n",7>=3+4);
int a=5;
int r=a>0;
printf("r=%d\n",r);
return 0;
//输出结果
1
r=1
判断是否相等的==和!=的优先级比其他的低,而连续的关系运算是从左到右进行的
/*
5>3==6>4
6>5>4
a==b==6
a==b>0
*/
#include
int main()
{
printf("%d\n",5>3==6>4);
printf("%d\n",6>5>4);
int a,b=5;
printf("%d\n",a==b==6);
printf("%d\n",a==b>0);
return 0;
}
//输出结果
1
0
0
1
7.if语句
//当if或者else语句后没有大括号的时候,只有后面那一句话是有效的
//计算薪水
#include
int main(){
const double RATE=8.25;
const int STANDARD=40;
double pay=0.0;
int hours;
printf("请输入工作的小时数:");
scanf("%d",&hours);
if(hours>STANDARD)
pay=STANDARD*RATE+(hours-STANDARD)*(RATE*1.5);
else
pay=hours*RATE;
printf("应付工资:%f\n",pay);
return 0;
}
8.switch-case
switch(控制表达式){
case 常量:
语句
.....
case 常量:
语句
.....
case 常量:
语句
.....
default:
语句
.....
}
//这个控制表达式只能是整数型的结果
//常量可以是常数,也可以是常数计算的表达式
/*case 1+1:
or
*/
/*const int MRN=2;
case MRN:
*/
break作用就是如果没有break就会顺序执行到下面的case里去,直到遇到一个break,或者switch结束为止
#include
int main(){
int type;
scanf("%d",&type);
switch(type){
case 1:
case 2:
printf("hello");
break;
case 3:
printf("good morning\n");
case 4:
printf("goodbye");
break;
default:
printf("what's up");
break;
}
return 0;
}
//输出结果
1
hello
2
hello
3
good morning
goodbye
//NOTE:case只是一个入口,break才是出口
9.while循环和do while循环
//判断一个正数是几位数
#include
int main(){
int x;
int n=0;
scanf("%d",&x);
n++;
//x/=10依次划去末尾的数
x/=10;
while(x>0){
n++;
x/=10;
}
printf("%d\n",n);
return 0;
}
在进入循环的时候不做检查,而是在执行完一轮循环体的代码之后,再来检查循环的条件是否满足,如果满足则继续下一轮循环,不满足则结束循环
do
{
<循环体语句>
}while(<循环条件>);
//上边的代码如果用do-while来写
#include
int main(){
int x;
int n=0;
scanf("%d",&x);
/*n++;
//x/=10依次划去末尾的数
x/=10;
while(x>0){
n++;
x/=10;
}*/
do{
x/=10;
n++;
}while(x>0);
//执行do-while,循环体至少执行一次,就算结果不满足循环条件也会执行一次循环体
printf("%d\n",n);
return 0;
}
10.猜数游戏
NOTE:%100
x%n的结果是[0,n-1]的一个整数
#include
#include
#include
int main(){
//rand()产生一个随机数
//rand()%100能随机产生一个0-99的随机数
//rand()%100+1能随机产生一个1-100的随机数
srand(time(0));
int count=0;
int number=rand()%100+1;
int a;
do{
printf("请输入一个0-100的数");
scanf("%d",&a);
count++;
if(a>number){
printf("输入的数大了");
}else if(a<number){
printf("输入的数小了");
}
}while(a!=number);
printf("太棒了,你用了%d次猜中了",count);
return 0;
}
11.算平均数
#include
#include
#include
int main(){
int count=0;
int t=0;
double average=0.0;
int number=0;
while(number!=-1){
scanf("%d",&number);
if(number!=-1){
count++;
t+=number;
}else{
average=(1.0*t)/count;
printf("average=%f",average);
}
}
return 0;
}
12.整数逆序
#include
int main(){
//123-321
int x;
int digit;
scanf("%d",&x);
//123%10=3
//123/10=12
//12%10=2;
//12/10=1
//1%10=1
while(x>0){
digit=x%10;
printf("%d",digit);
x/=10;
}
return 0;
}
13.for循环次数
#include
int main(){
int i;
for(i=0;i<5;i++){
printf("i=%d",i);
}
printf("\n最后i=%d\n",i);
return 0;
//等价于
/*
for(i=1;i<=5;i++){
printf("i=%d",i);
}
printf("\n最后i=%d\n",i);
return 0;
}
*/
14.break和continue区别
break直接跳出循环,continue是跳出此次循环,执行下一次循环
//break直接跳出循环
//continue是跳出此次循环,执行下一次循环
#include
int main(){
int i;
for(i=0;i<5;i++){
printf("%d",i);
continue;
printf("hello world");
}
return 0;
}
//输出结果
0
1
2
3
4
#include
int main(){
int i;
for(i=0;i<5;i++){
printf("%d\n",i);
break;
printf("hello world");
}
return 0;
}
//输出结果
0
15.输出100以内的所有素数
#include
int main(){
int x;
for(x=1;x<100;x++){
int i;
int isprime=1;
for(i=2;i<x;i++){
if(x%i==0){
isprime=0;
break;
}
}
if(isprime==1){
printf("%d\n",x);
}
}
return 0;
}
16.凑钱游戏
#include
int main(){
//如何用1角,2角,5角的硬币凑出10元以下的金额
int x;
int one,two,five;
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+two*2+five*5==x*10){
printf("可以用%d个一角和%d个两角和%d个五角凑成%d个一元\n",one,two,five,x);
}
}
}
}
return 0;
}
//输出结果
2
可以用1个一角和2个两角和3个五角凑成2个一元
可以用1个一角和7个两角和1个五角凑成2个一元
可以用2个一角和4个两角和2个五角凑成2个一元
可以用3个一角和1个两角和3个五角凑成2个一元
可以用3个一角和6个两角和1个五角凑成2个一元
可以用4个一角和3个两角和2个五角凑成2个一元
可以用5个一角和5个两角和1个五角凑成2个一元
可以用6个一角和2个两角和2个五角凑成2个一元
可以用7个一角和4个两角和1个五角凑成2个一元
可以用8个一角和1个两角和2个五角凑成2个一元
可以用9个一角和3个两角和1个五角凑成2个一元
可以用11个一角和2个两角和1个五角凑成2个一元
可以用13个一角和1个两角和1个五角凑成2个一元
//使用break使其输出一种情况就可以了
#include
int main(){
//如何用1角,2角,5角的硬币凑出10元以下的金额
int x;
int one,two,five;
scanf("%d",&x);
int exit=0;
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+two*2+five*5==x*10){
printf("可以用%d个一角和%d个两角和%d个五角凑成%d个一元\n",one,two,five,x);
exit=1;
break;
}
}
if(exit)break;
}
if(exit)break;
}
return 0;
}
//输出结果
2
可以用1个一角和2个两角和3个五角凑成2个一元
上面叫做接力break
也可以使用goto语句,如下:
#include
int main(){
//如何用1角,2角,5角的硬币凑出10元以下的金额
int x;
int one,two,five;
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+two*2+five*5==x*10){
printf("可以用%d个一角和%d个两角和%d个五角凑成%d个一元\n",one,two,five,x);
goto out;
}
}
}
}
out:
return 0;
}
//输出结果
2
可以用1个一角和2个两角和3个五角凑成2个一元
17.求前n项和
//1+1/2+1/3+1/4+...+1/n
#include
int main(){
int n;
scanf("%d",&n);
int i;
double sum=0.0;
for(i=1;i<=n;i++){
sum+=1.0/i;
}
printf("f(%d)=%f",n,sum);
return 0;
}
//1-1/2+1/3-1/4+1/5-...+1/n
#include
int main(){
int n;
scanf("%d",&n);
int i;
double sum=0.0;
double sign=1.0;
for(i=1;i<=n;i++){
sum+=sign/i;
sign=-sign;
}
printf("f(%d)=%f\n",n,sum);
return 0;
}
18.数据类型
//整数
char,short,int,long,long long
//浮点数
float,double,long double
//逻辑
bool
//指针
//自定义类型
//sizeof是一个运算符,给出某个类型或变量在内存所占据的字节数
sizeof(int)//int在内存中占据几个字节
sizeof(i)//i这个变量在内存中占据几个字节
#include
int main(){
int a;
a=6;
printf("sizeof(int)=%lf\n",sizeof(int));
printf("sizeof(double)=%lf\n",sizeof(double));
printf("sizeof(a)=%lf\n",sizeof(a));
return 0;
}
//输出结果
sizeof(int)=4
sizeof(double)=8
sizeof(a)=4
#include
int main(){
printf("sizeof(char)=%ld\n",sizeof(char));
printf("sizeof(short)=%ld\n",sizeof(short));
printf("sizeof(int)=%ld\n",sizeof(int));
printf("sizeof(long)=%ld\n",sizeof(long));
printf("sizeof(long long)=%ld\n",sizeof(long long));
return 0;
}
//输出结果
sizeof(char)=1
sizeof(short)=2
sizeof(int)=4
sizeof(long)=8
sizeof(long long)=8
19.unsigned
//unsigned初衷并非扩展数能表达的范围,而是为了做纯二进制运算,主要是为了移位
#include
int main(){
char c=255;
//255二进制为11111111,2的8次方-1
//没有unsigned表示最高位是符号位,所以输出结果是-1
printf("%d",c);
return 0;
}
//输出结果
-1
#include
int main(){
unsigned char c=255;
printf("%d",c);
return 0;
}
//输出结果
255
20.整数的输入输出
整数的输入输出只有两种形式:int和long long
%d:int
%u:unsigned
%ld:long long
%lu:unsigned long long
21.输出精度
float有7位是有效的,double有15为有效数字
//在%和f之间加上.n可以指定输出小数点后几位,这样的输出是做4舍5入的
//%3.0f 前一个数字是字符宽,后一个数是小数点位数
#include
int main(){
printf("%.3f\n",-0.0049);
printf("%.30f\n",-0.0049);
printf("%.3f\n",-0.00049);
return 0;
}
//输出结果
-0.005
-0.004899999999999999800000000000
-0.000
22.超过范围的浮点数
//printf输出inf表示超过范围的浮点数
//printf输出nan表示不存在的浮点数
#include
int main(){
printf("%f\n",12.0/0.0);
printf("%f\n",-12.0/0.0);
printf("%f\n",0.0/0.0);
return 0;
}
//输出结果
inf
-inf
nan
带小数点的字面量是double而非float,float需要用f或F后缀
f1==f2可能失败
所以用
fabs(f1-f2)<1e-12//f1-f2的绝对值小于1e-12
如果没有特殊要求,只是用double,现代cpu能直接对double作硬件计算,性能不会比float差,在64位的机器上,数据存储的速度也不会比float慢
23.字符类型
/*
1.char是一种整数,也是一种特殊的类型:字符。这是因为:
2.用单引号表示字符字面量:'a','1'
3.''也是一个字符
4.printf和scanf里用%c来输入输出字符
*/
#include
int main(){
char c;
char d;
c=1;
d='1';
if(c==d){
printf("相等\n");
}else {
printf("c=%d\n",c);
printf("d=%d\n",d);
}
return 0;
}
//输出结果
不相等
c=1
d=49
//如何输入'1'这个字符给char c
//两种方式,如下
//方法1
#include
int main(){
char c;
scanf("%c",&c);
printf("c=%d\n",c);
printf("c='%c'\n",c);
return 0;
}
//输出结果
1
c=49
c='1'
//方法2
#include
int main(){
char c;
int d;
//因为scanf里%d,所以只能用int接受,所以用一个int整形的变量d
scanf("%d",&i);
d=i;
printf("c=%d\n",c);
printf("c='%c'\n",c);
return 0;
}
//输出结果
49
c=49
c='1'
%d %c中间的空格意思是直到后面读到的不是空格为止
#include
int main(){
char c;
int i;
scanf("%d %c",&i,&c);
printf("i=%d c=%d c='%c'\n",i,c,c);
return 0;
}
//输出结果
12 1
i=12 c=49 c='1'
#include
int main(){
char a;
int b;
scanf("%d%c",&a,&b);
printf("a=%d b=%d b='%c'\n",a,b,b);
return 0;
}
//输出结果
12 1
a=12 b=32 b=' '
24.逃逸字符
/*
\b回退一格
\t到下一个表格位
\n换行
\r回车
\''双引号
\'单引号
\\反斜杠本身
*/
#include
int main(){
printf("123\bA\n456\n");
return 0;
}
//输出结果
12A
456
25.自动类型转换
对于printf,任何小于int的类型都会被转换成int;float会被转换成double,所以%f就可以输出double,不需要%lf,因为%f在printf里自动被转换成了%lf。但是scanf不会,要输入short,需要%hd。
26.强制类型转换
要把一个量强制转换为另一个类型(通常是较小的类型),需要:(类型)值
//(int)10.2
//(short)32
注意这时候的安全性,小的变量不总能表达大的量
//(short)32768
//强制类型转换的优先级高于四则运算
double a=1.0;
double b=2.0;
int i=(int)a/b;
//int i=(int)(a/b);
26.bool类型
#include
//之后就可以使用bool和true,false
27.条件运算和逗号运算
m<n ? x : a+5//如果m
a++>=1 && b-->2 ? a:b//如果两个都满足结果是a,否则是b
//逗号表达式主要是在for条件中使用,基本上就只有这一个用处
for(i=10,j=5;i<=20;i++,j--)
28.函数传值
每个函数都有自己的变量空间,参数也位于这个独立的空间中,和其他的函数没有关系
过去,对于函数参数表中的参数,叫做“形式参数”,调用函数时给出的值,叫做“实际参数”,我们不建议继续用这种古老的方式称呼它们
现在,我们就把叫做参数和值,这中间发生的就只有一件事情,就是传值
29.本地变量(局部变量)
30.数组的例子:统计个数
//写一个程序,输入数量不确定的[0,9]范围内的整数,统计每一种数字出现的次数,输入-1表示结束
#include
int main(){
int i;
int x;
int count[10];
//初始化数组
for(i=0;i<10;i++){
count[i]=0;
}
scanf("%d",&x);
while(x!=-1){
if(x>=0 && x<=9){
count[x]++;
}
scanf("%d",&x);
}
for(i=0;i<10;i++){
printf("%d:%d\n",i,count[i]);
}
return 0;
}
31.数组运算
数组初始化
#include
int main(){
int i;
//注意这种写法
int number[10]={[1]=2,4,[5]=6};
for(i=0;i<10;i++){
printf("%d\t",number[i]);
}
return 0;
}
//输出结果
0 2 4 0 0 6 0 0 0 0
数组的大小
sizeof(a)/sizeof(a[0]);
//sizeof(a[0])给出单个元素的大小,于是相除就是数组的单元个数
//这样的代码,一旦修改数组中初始的数据,不需要修改遍历的代码
数组的赋值
数组变量本身不能被赋值
要把一个数组的所有元素交给另外一个数组,必须采用遍历
#include
int main(){
int a[]={1,2,3};
//int b[]=a;//这是错误的,只能采用遍历
for(i=0;i<length;i++){
b[i]=a[i];
}
return 0;
}
//判断数组中数字的下标
#include
int main(){
int a[]={1,2,3,4,6};
int x;
int loc;
printf("请输入一个数字:");
scanf("%d",&x);
loc=search(x,a,sizeof(a)/sizeof(a[0]));
if(loc!=-1){
printf("%d在第%d个位置上\n",x,loc);
}else{
printf("不存在");
}
return 0;
}
int search(int key,int a[],int length){
int i;
int ret=-1;
for(i=0;i<length;i++){
if(a[i]==key){
ret=i;
break;
}
}
return ret;
}
//输出结果
请输入一个数字:2
2在第1个位置上
32.二维数组
int a[3][5]是一个三行五列的矩阵
//二维数组的遍历
//比如
for(i=0;i<3;i++){
for(j=0;j<5;j++){
a[i][j]=i*j;
}
}
//二维数组初始化
int a[][5]={
{0,1,2,3,4},
{2,3,4,5,6},
}
//列数是必须给出的,行数可以由编译器来数
//每行一个{},逗号分离
//最后的逗号可以存在,有古老的传统
//如果省略,表示补0
33.取地址计算
1.运算符&,获得变量的地址,他的操作数必须是变量。
2.int i;printf("%x",&i);
3.地址的大小是否与int相同取决于编译器
4.int i;printf("%p",&i);
5.&不能对没有地址的东西取地址
34.指针
/*
指针:就是保存地址的变量
int i;
int* p=&i;
//这两种写法都是说明p是一个指针,而q是一个int类型的变量,只是写法不同而已
int* p,q;
int *p,q;
//p和q都是指针
int *p,*q;
*********************************
作为参数的指针
void f(int *p);
在被调用的时候得到了某个变量的地址:
int i=0;f(&i);
在函数里面可以通过这个指针访问外面的这个i
*/
#include
void f(int *p);
int main(){
int i=6;
printf("&i=%p\n",&i);
f(&i);
return 0;
}
void f(int *p){
printf("p=%p\n",p);
}
//输出结果
&i=000000000062FE4C
p=000000000062FE4C
/*
访问那个地址上的变量*
1.*是一个单目运算符,用来访问指针的值所表示的地址上的变量
2.可以做右值也可以做左值
//*的意思是访问指针所指向那个地址的变量
3.int k=*p;
4.*p=k+1;
5.*p这个整体可以当做一个变量值
*/
指针的运算符
/*
1.互相反作用
2.*&yptr->*(&yptr)->*(yptr的地址)->得到那个地址上的变量->yptr
3.&*yptr->&(*yptr)->&(y)->得到y的地址,也就是yptr->yptr
*/
指针有什么作用
/*
1.指针应用场景一:交换两个变量的值
void swap(int *pa,int *pb)
{
int t=*pa;
*pa=*pb;
*pb=t;
}
*/
#include
int main(){
int a=5,b=3;
swap(&a,&b);
printf("a=%d,b=%d\n",a,b);
return 0;
}
void swap(int *pa,int *pb){
int t=*pa;
*pa=*pb;
*pb=t;
}
//输出结果
a=3,b=5
/*
2.指针应用场景二
函数返回多个值,某些值就只能通过指针返回
传入的参数实际上是需要保存带回的结果的变量
*/
#include
void minmax(int a[],int len,int *min,int *max);
int main(){
int a[]={1,2,3,4,5,6,7,8,9,13,45,67};
int min,max;
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
printf("min=%d,max=%d\n",min,max);
return 0;
}
void minmax(int a[],int len,int *min,int *max){
int i;
*min=*max=a[0];
for(i=1;i<len;i++){
if(a[i]<*min){
*min=a[i];
}
if(a[i]>*max){
*max=a[i];
}
}
}
//输出结果
min=1,max=67
/*
指针应用场景二b
1.函数返回运算的状态,结果通过指针返回
2.常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错
3.-1或0(在文件操作会看到大量的例子)
4.但是当任何数值都是有效的可能结果时,就得分开返回了(状态通过retun来返回,结果通过指针来返回)
*/
#include
int main(){
int a=5;
int b=2;
int c;
if(divide(a,b,&c)){
printf("%d/%d=%d\n",a,b,c);
}
return 0;
}
int divide(int a,int b,int *result){
int ret=1;
if(b==0)ret=0;
else{
*result=a/b;
}
return ret;
}
指针最常见的错误
1.定义了指针变量,还没有指向任何变量,就开始使用指针
比如
int *p;
*p=12;是错误的
35.指针与数组
/*
1.函数参数表中的数组实际上是指针
2.sizeof(a)==sizeof(int*)
3.但是可以用数组的运算符[]进行运算
*/
数组参数
/*
以下四种函数原型是等价的
1.int sum(int *ar,int n);
2.int sum(int *,int);
3.int sum(int a[],int n);
4.int sum(int [],int);
数组变量是特殊的指针
1.数组变量本身表达地址,所以
int a[10];int* p=a;//无需用&取地址
但是数组的单元表达的是变量需要用&取地址
a==&a[0]
2.[]运算符可以对数组做,也可以对指针做:
p[0]<==>a[0]
如果p是数组,*p表示的就是第一个数组元素的地址,所以*p和p[0]是等价的
3.*运算符可以对指针做,也可以对数组做:
*a=25;
4.数组变量是const的指针,所以不能被赋值
//数组变量之间是不能做互相赋值的
int a[];
int b[];
b=a//错误
*/
//表示一旦得到了某个变量的地址,不能再指向其他变量
int *const q=&i;//q是const
*q=26;//OK
q++;//error,q是i的地址,q指向i这个事实不能被改变了
//所指是const
1.表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)
2.const int* p=&i;
3.*p=26;//ERROR(*p)是const
4.i=26;//OK
5.p=&i;//OK
NOTE:注意区分两者的区别
这些是啥意思
int i;
const int* p1=&i;
int const* p2=&i;
//前面两种const都是在*号的前面,所以意思是一样的,即不能通过这个指针去修改i这个变量,可以理解为*p1是const,即窒息那个的i这个数据是不能再修改了
int *const p3=&i;
//const在*号后面,说明指针不能被修改,即指向了i这个地址,不能再指向其他变量的地址
判断哪个被const的标志是const在*号的前面还是后面
转换
总是可以把一个非const的值转换成const的
void f(const int *x);
int a=14;
f(&a);//ok
const int b=a;
f(&b);//ok
b=a+1;//ERROR
当要传递的参数的类型比地址大的时候,这是常用的手段;即能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改
const数组
1.const int a[]={1,2,3,4};
2.数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int
3.所以必须通过初始化进行赋值
保护数组值
1.因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值
2.为了保护数组不被函数破坏,可以设置参数为const
3.int sum(const int a[],int length); //这样的话,在sum这个函数里就不能对a这个数组作任何的修改
37.指针运算
/*
1.这些算术运算可以对指针做
2.给指针加、减一个整数(+,+=,-,-=)
3.递增递减(++/--)
4.两个指针可以相减
*p++
//取出p所指那个地方的东西,顺便把p+1
1.取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
2.*的优先级虽然高,但是没有++高
3.常用于数组类的连续空间操作
4.在某些CPU上,这可以直接被翻译成一条汇编指令
*/
#include
int main(){
int a[]={1,2,3,4,5,-1};
int *p;
p=a;
//用*p++遍历数组
while(*p!=-1){
printf("%d\t",*p++);
}
return 0;
}
//输出结果
1 2 3 4 5
指针比较
1.<,<=,==,>,>=,!=都可以对指针做
2.比较它们在内存中的地址
3.数组中的单元的地址肯定是线性递增的
0地址
1.当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
2.所以你的指针不应该具有0值
3.因此可以用0地址来表示特殊的事情
返回的指针是无效的
指针没有被真正初始化(先初始化为0)
4.NULL是一个预定定义的符号,表示0地址
5.有的编译器不愿意你用0来表示0地址
指针的类型转换
1.void* 表示不知道指向什么东西的指针
计算时与char*相同(但不相通)
2.指针也可以转换类型
int *p=&i;void* q=(void*)p;
3.这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量
我不再当你是int啦,我认为你就是void!
用指针来做什么
1.需要传入较大的数据时作为参数
2.传入数组后对数组做操作
3.函数返回不止一个结果
4.需要用函数来修改不止一个变量
5.动态申请的内存...
38.动态内存分配
malloc
#include
void* malloc(size_t size);
1.向malloc申请的空间大小是以字节为单位的
2.返回的结果是void*,需要类型转换为自己需要的类型
3.(int*)malloc(n*sizeof(int));
free(name);
//example
#include
#include
int main(){
int number;
int *a;
int i;
printf("输入数量:\n");
scanf("%d",&number);
a=(int*)malloc(number*sizeof(int));
for(i=0;i<number;i++){
scanf("%d",&a[i]);
}
for(i=number-1;i>=0;i--){
printf("%d",a[i]);
}
free(a);
return 0;
}
//输出结果
输入数量:
3
1
2
3
321
free
1.把申请得来的空间还给系统
2.申请过的空间,最终都应该要还
3.只有还申请来的空间的首地址
写程序的好习惯,任何的指针定义出来的,就初始为0
void* p=0;
free(p);//这种情况没有问题
常见的问题
1.申请了没free->长时间运行内存逐渐下降
2.新手:忘了
3.老手:找不到合适的free的时机
4.free过了再free,会崩溃
39.字符串
//字符数组
char word[]={'H','E','L','L','O'};
word[0]=H
word[1]=...
//字符串
char word[]={'h','e','l','l','l','\0'};
这个\0就使得这个word是个字符串,虽然仍是个字符数组,但是可以当字符串来进行运算
字符串
'\0’和’0’是不一样的,‘0’代表是ASCII的零,而’\0’代表的就是0
//字符串变量
char *str="hello";
char word[]="hello";
char line[10]="hello";
//字符串常量
example:"hello"
1."hello"会被编译器变成一个字符数组放在某处,这个数组的长2.度是6,结尾还有表示结束的0
3.两个相邻的字符串常量会被自动连接起来
//字符串
1.C语言的字符串是以字符数组的形态存在的
2.不能用运算符对字符串做运算
3.通过数组的方式可以遍历字符串
4.唯一特殊的地方是字符串字面量可以用来初始化字符数组
5.以及标准库提供了一系列字符串函数
40.字符串常量
char* s=“hello world”;
1.s是一个指针,初始化为指向一个字符串常量
2.由于这个常量所在的地方,所以实际上s是const char* s,但是由于历史的原因,编译器接受不带const的写法
3.但是试图对s所指的字符串做写入会导致严重的后果
4.如果需要修改字符串,应该用数组
char s[]=“hello world”;
用指针还是数组?
1.char *str=“hello”;
2.char str[]=“hello”;
3.数组:这个字符串在这里
作为本地变量空间自动被回收
4.指针:这个字符串不知道在哪里
就是表达一个字符串,只读的不去写
处理参数
动态分配空间
NOTE:
如果要构造一个字符串->数组
如果要处理一个字符串->指针
char*是字符串
1.字符串可以表达为char*的形式
2.char*不一定是字符串
本意是指向字符的指针,可能指向的是字符的数组(就像int*一样)
3.只有它所指的字符数组有结尾的0,才能说它所指的是字符串
41.字符串输入输出
/*
char string[8];
scanf("%s",string);
printf("%s",string);
scanf读入一个单词(到空格、tab或回车为止)
scanf是不安全的,因为不知道要读入的内容的长度
char string[8];
scanf("%7s",string);//告诉机器最多读入7个字符,多于7个的就不要了
*/
#include
#include
void f(){
char str1[8];
char str2[8];
scanf("%7s",str1);
scanf("%7s",str2);
printf("%s##%s\n",str1,str2);
}
int main(){
f();
return 0;
}
//输出结果
12345678
1234567##8
/*
安全的输入
char string[8];
scanf("%7s",string);
1.在%和s之间的数字表示最多允许读入的字符的数量,这个数字应该比数组的大小小1
2.下一次scanf从哪里开始?
常见错误
char* string;//含义是定义了一个指针变量string
scanf("%s",string);
1.以为char*是字符串类型,定义了一个字符串类型的变量string就可以直接使用了
2.由于没有对string初始化为0,所以不一定每次运行都出错
空字符串
1.char buffer[100]="";
这是一个空的字符串,buffer[0]=='\0'
2.char buffer[]="";
这个数组的长度只有1!
*/
42.字符串数组
/*
1.char **a
a是一个指针,指向另一个指针,那个指针指向一个字符(串)
2.char a[][]
char a[][10];//意思是a[0]-->char [10]每个数组元素是一个字符长度为10的字符串
char a[][10]={
"hello",
"world",
"python"
};
3.还有一种写法是
char *a[]={
"hello",
"world",
"python"
};
此时a[0]-->char*
*/
43.单字符的输入输出
/*
putchar
1.int putchar(int c);
2.向标准输出写一个字符
3.返回写了几个字符,EOF(-1)表示写失败
getchar
1.int getchar(void);
2.从标准输入读入一个字符
3.返回类型是int是为了返回EOF(-1)
4.windows--->Ctrl-z
unix--->Ctrl-D
*/
#include
int main(){
int ch;
while((ch=getchar())!=EOF){
putchar(ch);
}
return 0;
}
44.字符串函数strlen
/*
string.h
1.strlen
2.strcmp
3.strcpy
4.strcat
5.strchr
6.strstr
strlen
1.size_t strlen(const char*s);
2.返回s 的字符串长度(不包括结尾的0)
*/
#include
#include
int main(){
char str[]="hello";
printf("%lu\n",strlen(str));
printf("%lu\n",sizeof(str));
return 0;
}
//输出结果
5
6
/*
strcmp
1.int strcmp(const* char *s1,const char* s2);
2.比较两个字符串,返回:
0:s1==s2
1:s1>s2
-1:s1
/*
strcpy
1.char *strcpy(char *restrict dst,const char* restrict src);//目的在前,源在后
2.把src的字符串拷贝到dst
restrict表明src和dst不重叠(C99)
3.返回dst
为了能链起代码来
//复制一个字符串
char *dst=(char*)malloc(strlen(src)+1);
strcpy(dst,src);
*/
#include
#include
char *mypy(char *dst,const char *src){
int idx=0;
//数组版
while(src[idx]){
dst[idx]=src[idx];
idx++;
}
dst[idx]='\0';
//指针版
/*
char *ret=dst;
while(*src){
*dst=*src;
dst++;
src++;
}
*dst='\0';
return ret;*/
}
int main(){
char s1[]="abc";
char s2[]="";
mypy(s2,s1);
printf("%s\n",s2);
return 0;
}
//输出结果
abc
/*
strcat
1.char* strcat(char *restrict s1,const char*restrict s2);
2.把s2拷贝到s1的后面,接成一个长的字符串
3.返回s1
4.s1必须具有足够的空间
安全问题
strcpy和strcat都可能出现安全问题
如果目的地没有足够的空间?
建议尽可能不要使用这两个函数
安全版本
1.char * strncpy(char *restrict dst,const char* restrict src,size_t n);
2.char *strncat(char* restrict s1,const char *restrict s2,size_t n);
3.int strncmp(const char *s1,const char *s2,size_t n);//这个作用是比较前n个
*/
45.字符串搜索函数
/*
字符串中找字符
char *strchr(const char* s,int c);//从左边找
char *strrchr(const char* s,int c);//从右边找
返回NULL表示没有找到
如何寻找第2个?
*/
#include
#include
int main(){
char s[]="hello";
char *p=strchr(s,'l');
p=strchr(p+1,'l');
printf("%s\n",p);
return 0;
}
//输出结果
lo
#include
#include
int main(){
char s[]="hello";
char *p=strchr(s,'l');
char *t=(char*)malloc(strlen(p)+1);
p=strchr(p+1,'l');
t=strcpy(t,p);
printf("%s\n",t);
return 0;
}
//输出结果
lo
#include
#include
int main(){
char s[]="hello";
char *p=strchr(s,'l');
char c=*p;
*p='\0';
char *t=(char*)malloc(strlen(s)+1);
p=strchr(p+1,'l');
t=strcpy(t,s);
printf("%s\n",t);
free(t);
return 0;
}
//输出结果
he
/*
字符串中找字符串
char *strstr(const char *s1,const char* s2);
char *strcasestr(const char *s1,const char *s2);//忽略大小写做寻找
*/
46.枚举
/*
1.枚举是一种用户定义的数据类型,它用关键字enum以如下语法来声明
enum 枚举类型名字{name1,name2,...,name3};
2.枚举类型名字通常并不真的使用,要用的是在大括号里的名字,因为它们就是常量符号,它们的类型是int,值则依次从0到n。如:
enum colors{red,yellow,green};
3.这样就创建了3个常量,red的值是0,yellow是1,而green是2.
4.当需要一些可以排列起来的常量值时,定义枚举的意义就是给了这些常量值名字。
*/
#include
enum color{red,yellow,green};//相当于定义一个color变量类型,其实里面的red,yellow,green相当于是int类型
void f(enum color c);
int main(){
enum color t=red;//定义一个color类型的变量t,把red赋值给t,C语言中这个enum不能省略,C++中可以省略
scanf("%d",&t);
f(t);
return 0;
}
void f(enum color c){
printf("%d\n",c);
}
//套路:自动计数的枚举
#include
enum COLOR{RED,YELLOW,GREEN,NumCOLORS};
int main(){
int color=-1;
char *ColorNames[NumCOLORS]={"red","yellow","green"};
char *colorName=NULL;
printf("请输入你喜欢的颜色代码:");
scanf("%d",&color);
if(color>=0&&color<NumCOLORS){
colorName=ColorNames[color];
}else{
colorName="unknown";
}
printf("你喜欢的颜色是%s\n",colorName);
return 0;
}
//输出结果
请输入你喜欢的颜色代码:0
你喜欢的颜色是red
/*
枚举量
1.声明枚举量的时候可以指定值
2.enum COLOR{RED=1,YELLOW,GREEN=5};
*/
#include
enum COLOR{RED=3,YELLOW,GREEN=5};
int main(){
printf("GREEN=%d\n",GREEN);
return 0;
}
//输出结果
GREEN=5
/*
枚举只是int
1.虽然枚举类型可以当做类型使用,但是实际上很少使用,可以说不好用
2.如果有意义上排比的名字,用枚举比const int方便
3.枚举比宏(macro)好,因为枚举有int类型
*/
47.结构类型
struct date{
int month;
int day;
int year;
};
//别忘了最后这个分号
/*
声明结构的形式:第一种形式
struct point{
int x;
int y;
};
struct point p1,p2;
p1和p2都是point,里面有x和y的值
第二种形式:
struct{
int x;
int y;
}p1,p2;
p1和p2都是一种无名结构,里面有x和y
第三种形式:
struct point{
int x;
int y;
}p1,p2;
p1和p2都是point里面有x和y的值
*/
#include
struct date{
int month;
int day;
int year;
};
int main(){
//第一种初始化方式
struct date today={07,31,2021};
//第二种初始化方式
struct date thismonth={.month=7,.year=2021};
printf("today's date is %i-%i-%i.\n",today.month,today.day,today.year);
printf("this month is %i-%i-%i.\n",thismonth.year,thismonth.month,thismonth.day);
return 0;
}
//输出结果
today's date is 7-31-2021.
this month is 2021-7-0.
结构成员
1.结构和数组有点像
2.数组用[]运算符合下标访问其成员
a[0]=10;
3.结构用.运算符和名字访问其成员
today.day
student.firstName
p1.x
p1.y
结构运算
1.要访问整个结构,直接用结构变量的名字
2.对于整个结构,可以做赋值、取地址,也可以传递给函数参数
p1=(struct point){5,10};//相当于p1.x=5;p1.y=10;
p1=p2;//相当于p1.x=p2.x;p1.y=p2.y;
结构指针
1.和数组不同,结构变量的名字并不是结构变量的地址,必须使用&运算符
2.struct date *pDate=&today;
48.结构与函数
/*
结构作为函数参数
int numberOfDays(struct date d)
1.整个结构可以作为参数的值传入函数
2.这时候是在函数内新建一个结构变量,并复制调用者的结构的值
3.也可以返回一个结构
4.这与数组完全不同
输入结构
1.没有直接的方式可以一次scanf一个结构
2.如果我们打算写一个函数来读入结构
3.->
4.但是读入的结构如何送回来呢?
5.记住C在函数调用时是传值的
所以函数中的p与main中的y是不同的
在函数读入了p的数值之后,没有任何东西回到main,所以y还是{0,0}
*/
#include
struct point{
int x;
int y;
};
void getstruct(struct point);
void output(struct point);
int main(){
struct point y={0,0};
getstruct(y);
output(y);
return 0;
}
void getstruct(struct point p){
scanf("%d",&p.x);
scanf("%d",&p.y);
printf("%d,%d",p.x,p.y);
}
void output(struct point p){
printf("%d,%d",p.x,p.y);
}
//输出结果
1 2
1,20,0
/*
解决的方案
1.之前的方案,把一个结构传入了函数,然后在函数中操作,但是没有返回回去
问题在于传入函数的是外面那个克隆体,而不是指针
传入结构和传入数组是不同的
2.在这个输入函数中,完全可以创建一个临时的结构变量,然后把这个结构返回给调用者
*/
#include
struct point{
int x;
int y;
};
struct point getstruct(void);
void output(struct point);
int main(){
struct point y={0,0};
y=getstruct();
output(y);
return 0;
}
struct point getstruct(void){
struct point p;
scanf("%d",&p.x);
scanf("%d",&p.y);
printf("%d,%d",p.x,p.y);
return p;
}
void output(struct point p){
printf("%d,%d",p.x,p.y);
}
//输出结果
1,2
1,21,2
/*
结构指针作为参数
指向结构的指针
struct date{
int month;
int day;
int year;
}myday;
struct date *p=&myday;
(*p).month=12;
p->month=12;
用->表示指针所指的结构变量的成员
*/
//改造上面的程序
#include
struct point{
int x;
int y;
};
struct point *getstruct(struct point *y);
int main(){
struct point p={0,0};
getstruct(&p);
print(&p);
output(p);
return 0;
}
struct point *getstruct(struct point *y){
scanf("%d",&y->x);
scanf("%d",&y->y);
printf("%d,%d\n",y->x,y->y);
return y;
}
//因为print函数只是输出值并不做修改,所以可以加const
void print(const struct point *y){
printf("%d,%d\n",y->x,y->y);
}
void output(struct point p){
printf("%d,%d\n",p.x,p.y);
}
//输出结果
1 2
1,2
1,2
1,2
49.结构数组
struct date dates[100];
struct date dates[]={
{4,5,2005},{2,4,2005}};
#include
struct time{
int hour;
int minutes;
int seconds;
};
struct time timeUpdate(struct time now);
int main(){
struct time testTimes[5]={
{11,59,59},{12,0,0},{1,2,3},{4,5,6},{6,7,8}
};
int i;
for(i=0;i<5;i++){
printf("Time is %.2i:%2i:%2i",testTimes[i].hour,testTimes[i].minutes,testTimes[i].seconds);
testTimes[i]=timeUpdate(testTimes[i]);
printf("...one second later it's %.2i:%.2i:%.2i\n",testTimes[i].hour,testTimes[i].minutes,testTimes[i].seconds);
}
return 0;
}
struct time timeUpdate(struct time now){
++now.seconds;
if(now.seconds==60){
now.seconds=0;
++now.minutes;
if(now.minutes==60){
now.minutes=0;
++now.hour;
if(now.hour==24){
now.hour=0;
}
}
}
return now;
}
//输出结果
Time is 11:59:59...one second later it's 12:00:00
Time is 12: 0: 0...one second later it's 12:00:01
Time is 01: 2: 3...one second later it's 01:02:04
Time is 04: 5: 6...one second later it's 04:05:07
Time is 06: 7: 8...one second later it's 06:07:09
/*
结构中的结构
struct dateAndTime{
struct date sdate;
struct time stime;
};
嵌套的结构
struct point{
int x;
int y;
};
struct rectangle{
struct point pt1;
struct point pt2;
};
如果有变量
struct rectangle r;
就可以有r.pt1.x
r.pt1.y
r.pt2.x
r.pt2.y
如果有变量定义:
struct rectangle r,*rp;
rp=&r;
那么下面四种形式是等价的:
r.ptl.x
rp->ptl.x
(r.ptl).x
(rp->prl).x
但是没有rp->ptl->x,因为ptl不是指针
*/
//结构中的结构的数组
#include
struct point{
int x;
int y;
};
struct rectangle{
struct point p1;
struct point p2;
};
void printRect(struct rectangle r){
printf("<%d,%d> to <%d,%d>\n",r.p1.x,r.p1.y,r.p2.x,r.p2.y);
}
int main(){
int i;
struct rectangle rects[]={
{{1,2},{3,4}},{{3,4},{5,6}}
};
for(i=0;i<2;i++){
printRect(rects[i]);
}
return 0;
}
//输出结果
<1,2> to <3,4>
<3,4> to <5,6>
50.类型定义
/*
自定义数据类型
1.C语言提供了一个叫做typedef的功能来声明一个已有的数据类型的新名字。比如:
typedef int Length;
使得Length成为int类型的别名。
这样,Length这个名字就可以代替int出现在变量定义和参数声明的地方了:
Length a,b,len;
Length numbers[10];
typedef
1.声明新的类型的名字
新的名字是某种类型的别名
改善了程序的可读性
typedef long int64_t;
typedef struct ADate{
int month;
int day;
int year;
}Date;//Date就代表了typedef和Date中间所有的东西
int64_t i=100000000;
Date d={9,1,2005};
typedef
typedef int Length;//Length就等价于int类型
typedef *char[10] Strings;//Strings是10个字符串的数组的类型
typedef struct node{
int data;
struct node*next;
}aNode;
或
typedef struct node aNode;//这样用aNode就可以代替struct node
*/
51.联合
略
52.全局变量
1.定义在函数外面的变量是全局变量
2.全局变量具有全局的生存期合作用域
它们与任何函数都无关
在任何函数内部都可以使用它们
#include
int f(void);
int gAll=12;
int main(){
printf("in %s gall=%d\n","main",gAll);
f();
printf("int %s gall=%d\n","main",gAll);
return 0;
}
int f(void){
printf("in %s gall=%d\n","f",gAll);
gAll=gAll+2;
printf("in %s gall=%d\n","f",gAll);
return gAll;
}
//输出结果
in main gall=12
in f gall=12
in f gall=14
int main gall=14
全局变量初始化
1.没有做初始化的全局变量会得到0值
指针会得到NULL值
2.只能用编译时刻已知的值来初始化全局变量
3.它们的初始化发生在main函数之前
被隐藏的全局变量
1.如果函数内部存在与全局变量同名的变量,则全局变量被隐藏
53.静态本地变量
//本来一个变量在进入函数时才有,离开函数时就消失了,加上static就变成静态本地变量,每次离开它还在那,再一次进入函数还是上一次离开函数时的值
1.在本地变量定义时加上static修饰符就成为了静态本地变量
2.当函数离开的时候,静态本地变量会继续存在并保持其值
3.静态本地变量的初始化只会在第一次进入这个函数时做,以后进入函数时会保持上次离开时的值
#include
int f(void);
int main(){
f();
f();
f();
return 0;
}
int f(void){
static int all=1;
printf("in %s all=%d\n","f",all);
all+=2;
printf("in %s all=%d\n","f",all);
return all;
}
//输出结果
in f all=1
in f all=3
in f all=3
in f all=5
in f all=5
in f all=7
静态本地变量
1.静态本地变量实际上是特殊的全局变量
2.它们位于相同的内存区域
3.静态本地变量具有全局的生存期,函数内的局部作用域
static在这里的意思是局部作用域(本地可访问)
53.后记:返回指针的函数,使用全局变量的贴士
*返回指针的函数
1.返回本地变量的地址是危险的
2.返回全局变量或静态变量的地址是安全的
53.后记:返回指针的函数,使用全局变量的贴士
*返回指针的函数
1.返回本地变量的地址是危险的
//如果函数里定义的是本地变量,最好不要返回本地变量的地址
2.返回全局变量或静态变量的地址是安全的
//全局变量或静态变量返回地址是可以的
3.返回在函数内malloc的内存是安全的,但是容易造成问题
4.最好的做法是返回传入的指针
tips
1.不要使用全局变量在函数间传递参数和结果
2.尽量避免使用全局变量
丰田汽车的案子
3.*使用全局变量和静态变量的函数是线程不安全的
54.宏定义
编译预处理指令
1.#开头的事编译预处理指令
2.它们不是C语言的成分,但是C语言程序离不开它们
3.#define用来定义一个宏
#include
#define PI 3.14
#define FORMAT "%f\n"
#define PI2 2*PI//pi*2
#define PRT printf("%f\n",PI);\
printf("%f",PI2)
int main(){
PRT;
return 0;
}
//输出结果
3.140000
6.280000
没有值的宏
1.#define _DEBUG
2.这类宏是用于条件编译的,后面有其他的编译预处理指令来检查这个宏是否已经被定义过了
55.带参数的宏
/*
像函数的宏
1.#define cube(x)((x)*(x)*(x))
2.宏可以带参数
*/
#include "stdio.h"
#define cube(x)((x)*(x)*(x))
int main(){
printf("%d\n",cube(5));
return 0;
}
//输出结果
125
/*
错误定义的宏
1.#define RADTODEG(x) (x*57.29578)
2.#define RADTODEG(x) (x)*57.29578
*/
/*
带参数的宏的原则
1.一切都要括号
整个值要括号
参数出现的每个地方都要括号
2.#define RADTODEG(x) ((x)*57.29578)
*/
/*
带参数的宏
1.可以带多个参数
#define MIN(a,b) ((a)>(b)?(b):(a))
2.也可以组合(嵌套)使用其他宏
3.在大型程序的代码中使用非常普遍
4.可以非常复杂,如“产生”函数
在#和##这两个运算符的帮助下
5.存在中西方文化差异
6.部分宏会被inline函数替代
*/
56.格式化输入输出
%[flags][width][.prec][hlL]type
flags:
-:左对齐
+:在前面放+或-
(space):整数留空
0:0填充
width或prec:
number:最小字符数
*:下一个参数是字符数
.number:小数点后的位数
.*:下一个参数是小数点后的位数
类型修饰:
hh:单个字节
h:short
l:long
ll:long long
L:long double
//FILE
1.FILE* fopen(const char* restrict path,const char* restrict mode);
2.int fclose(FILE* stream);
3.fscanf(FILE*,...);
4.fprintf(FILE*,...);
//打开文件的标准代码
FILE* fp=fopen("file","r");
if(fp){
fscanf(fp,...);
fclose(fp);
}else{
...
}
1.其实所有的文件最终都是二进制的
2.文本文件无非是用最简单的方式可以读写的文件
more、tail
cat
vi
3.而二进制文件是需要专门的程序来读写的文件
4.文本文件的输入输出是格式化,可能经过转码
//二进制读写
1.size_t fread(void *restrict ptr,size_t size,size_t nitems,FILE *restrict stream);
2.size_t fwrite(const void *restrict ptr,size_t size,size_t nitems,FILE *restrict stream);
3.注意FILE指针是最后一个参数
4.返回的是成功读写的字节数