C程 Ch1-8
约 4663 个字 221 行代码 预计阅读时间 18 分钟
Abstract
是之前转专业时候的自学的笔记。
一、语言基础
由CS50X补充得到的:
code hello.c
make hello
./hello
1~5 一些小细节
- 逗号运算:其一般形式为: 表达式1, 表达式2 其求值过程是分别求两个表达式的值,并以表达式2(即最右边的一个表达式)的值作为整个逗号表达式的值。
1.1 不熟悉的命令汇总
1.1.1 逻辑运算:
- 非:!
- and:&&
- or:||
- 非0值:真;0值:假;
1.1.2 switch的用法:
int data;
switch(data){
case 1://当data等于1的时候执行语句A
语句A;
break;//如果这里的break删了,下面的一个case中的语句B也会执行
case 2:
语句B;
break;
case 3:
语句C;
break;
case 4:
语句D;
break;
default://其它情况的时候执行语句E
语句E;
}
//常量不能是float、double等不为整数且无法自助转换为整数的结构类型(也可以是常量表达式)
1.1.3 其他
- / */* 和 // …… 都表示注释,前者可以注释多行
-
格式化输出字符
-
%d: 整数
-
%c:字符
- %s:字符串
- %f:浮点(%lf:双精度,double类型的数字输出必须要用lf或者le)
- %n.mf:
- n: 输出内容空间≥n,不足n,左边补空格
- m:小数部分m位,不足m右侧补0
- %n.mf:
- %md:输出宽度为m的整数
- %0md: 输出宽度为m的整数,不足的位置用0来填充
- %-8d: 47______ 左对齐
- 运算一般多为双目运算和单目运算,也有三目运算,例如 ?: 就是三目运算符
- C语言具有良好的可移植性。e.g. C语言没有输入输出程序,通过printf和scanf实现输入输出,为C在不同硬件平台上的可移植性提供了基础。
- C语言的不足:数据类型检查不严格、表达式有二义性、不能自动检查数据越界
- 在C语言中,表达式
*q++=10
的意思是先将10
赋值给指针q
指向的内存地址上的值,然后将指针q
后移一个位置 - scanf(“%d %c”)和scanf("%d%c")的区别
- scanf(“%d__%c”):会读数值以及数值之后的空格
- scanf("%d%c"):读到数字为止
- 编译效果如下:(空格的ACSII码:32) ![[Pasted image 20240214160322.png]]
-
-
逃逸字符
字符 | 意义 |
---|---|
\b | 回退一格 |
\t | 到下一个表格位 |
\n | 换行 |
\r | 回车 |
\" | 双引号 |
\' | 单引号 |
\ | 反斜杠本身 |
- 比如下面运行成果: | |
![[Pasted image 20240214161715.png]] |
1.1.4 抱佛脚用的C程面经
二、 数组
2.1 数组的定义、改变、提取、遍历
- 初始定义:int number[100] <类型> 数组名称[元素数量]
- e.g. double weight[20]
- 改变值:number[cnt]=x
- 提取值:number[i]>=???
- 遍历数组:
for (i=0;i<cnt;i++){ if(number[i]>average){ printf("%d",number[i]); } }
2.3 示例程序
##include <stdio.h>
int main()
{
int x;
double sum = 0;
int cnt=0;
int number[l00];
scanf("%d", & x);
while ( x != - 1 ) {
number[cnt]=x
sum+=x;
cnt++ ;
scanf("%d", &x);
}if (cnt > 0) {
printf("%f\n" ,sum/cnt);
int i;
for (i=0; i<cnt; i++ ){
if (number[i]) > sum/cnt ){
printf("%d\n",number[i]);
}
}
}
return 0;
}
2.2 二维数组
2.2.1 二维数组的定义和初始化
-
定义
int a[3][5]//定义一个3行5列的矩阵
-
初始化
int a[][5]={
{0,1,2,3,4},
{2,3,4,5,6},
}
-
注意:
- 列数是必须给出的,行数可以由编译器来数
- 每行一个},逗号分隔
- 最后的逗号可以存在,有古老的传统
- 如果省略,表示补零
-
数组的集成初始化
- int a[]={1,2,3,4,5,6} 即定义出了一个[1 2 3 4 5 6]的数组
- 数组会自己进行填充
- for e.g. 如果这么定义数组:int a[4]={2} 定义出的是[2 0 0 0]的数组
- 所以新建一个100个元素的空白数组,除了用循环依次定义,也可以
int number[l00]={0};
-
集成初始化的定位(只有C99)
int a[10] = { [0] =2, [2]=3,6, }
-
结果:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
2 | 0 | 3 | 6 | 0 | 0 | 0 | 0 | 0 | 0 |
2.2.2 计算数组初始的大小
- 利用sizeof函数:sizeof计算的是整个数组的大小,而sizeof[0]则是单个数组的大小,相除即可 sizeof(a)/sizeof(a[0])这样改了初始数据,不需要修改所有代码
2.2.3 二维数组和指针
- 二维数组名代表数组的起始地址,数组名加1,是移动一行元素。因此,二维数组名常被称为行地址。
- a+1 等效于 a[1],a+2 等效于 a[2]
2.2.4 二维数组的专属注意事项
- 注意事项:
- 用[n]在初始化数据中给出定位
- 没有定位的数据接在前面的位置后面
- 其他位置的值补零
- 也可以不给出数组大小,让编译器算(到有数据的最后一位)
- 特别适合初始数据稀疏的数组
- 二维数组会根据所给的列数进行补零操作,如:
##include<stdio.h> int main() { int a[][3]={{0},{1},{2}}; printf("%d",a[1][2]); return 0; }
- 生成的数组应该是:
0 | 0 | 0 |
---|---|---|
1 | 0 | 0 |
2 | 0 | 0 |
- 二维数组自动换行功能:??为什么会自动换行 | ||
|
-求输出结果? 依旧写一个数组:
1 | 4 | 7 |
---|---|---|
2 | 5 | 8 |
3 | 6 | 9 |
- 输出第二行,于是3,6,9 |
2.4 数组的注意事项
- 程序编译不会检验数组下标越界,这是程序员的职责[0~元素数量-1] 结果是segmentation fault
- 长度为0的数组,存在,但是无用
- from讨论区:字符可以做下标,默认识别成字母对应的ASCII码
- 数组不可以进行赋值,一个数组不可以赋值给另一个数组
三、函数
3.1 标准库函数:(###include )
- sqrt(double x);
- fabs(double x); 绝对值
- pow(double x, double y);幂函数
- exp(double x);自然指数
- log(double x);自然对数
3.2 函数定义/声明
3.2.1 函数声明
void sum(int begin, int end);
- 结尾的分号代表这句话是声明!
3.2.2 函数定义
//第一行是函数头
void sum(int begin, int end)//void->返回类型 sum->函数名
//void是空的意思,就是没有返回的数据
//(int begin, int end)->参数表
{
int i;
int sum =0;
for (i=begin;i<=end;i++){
sum+=i;
}
printf("%d到%d的和是%d\n",begin,end,sum);//中间叫函数体
}
调用函数 函数名(参数值)
3.2.3 对于函数类型的解释
- 没有返回值的函数,类型写void
- 不能使用带值的return,可以没有return,也可以直接写:
return;
3.3 完整程序呈现
##include <stdio.h>
void sum(int begin, int end);//函数声明
int main()
{
sum(1,10);
return 0;
}
void sum(int begin, int end)//函数定义
{
int i;
int sum =0;
for (i=begin;i<=end;i++){
sum+=i;
}
printf("%d到%d的和是%d\n",begin,end,sum);
}
3.4 函数特点:传递变量
- C语言在调用函数的时候,永远只能传值给函数,对函数外面的值没什么作用
运行结果:即使swap函数可以交换数值,依旧没有交换函数外面的a、b的值,因为里外无关 explaination:
void swap(int a, int b); int main( ) { int a = 5; int b = 6; swap(a,b); printf("a=%d b=%d\n", a,b); return 0; } void swap(int a, int b) { int t = a; a = b; b = t; }
- 函数的每次运行,就产生了一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,称作本地变量
- 定义在函数内部的变量就是本地变量
- 参数也是本地变量
- 生存期:什么时候这个变量开始出现了,到什么时候它消亡了
- 作用域:在(代码的)什么范围内可以访问这个变量 (这个变量可以起作用)
- 对于本地变量,这两个问题的答案是统一的:大括号内——块
- 变量只在大括号内存在,有意义
- 块里面的变量定义会覆盖块外面的,但同一代码块里不能重复定义相同的变量
3.5 函数的注意事项
- 若没有参数,原型一定要写全
- void f(void)->明确的没有参数
- void f()->只知道返回类型,参数未知
- int main()也是函数,返回的结果是return 0 报告给操作系统
2024.2.14 - 变量不可以一专多能,既用来遍历,又用来判断 - struct结构
//面额搜索的代码 struct!!
##include<stdio.h>
struct{
int amount;
char *name;
}coins[]={
{1,"penny"},
{5,"nickle"},
{10,"dime"},
{25,"quarter"},
{50,"half-dollar"}
};
int main()
{
int k;
int res=0;
printf("请输入纸币面额");
scanf("%d",&k);
int i;
for (i=0;i<sizeof(coins)/sizeof(coins[0]);i++){
if (coins[i].amount==k){
res=1;
printf("是%s\n",coins[i].name);
break;
}
}
if (res==0){
printf("没有对应纸币");
}
return 0;
}
4 指针
4.1 指针介绍
- 设 px 为一个指针,则 :
- px — 指针变量, 它的内容是地址量
- *px — 指针所指向的对象, 它的内容是数据
- &px — 指针变量占用的存储区域的地址,是个常量
4.2 地址与指针
4.2.1 存储容量
- int型变量:2个字节
- char型变量:1个字节
- float型变量:4个字节
- double型:8个字节
4.2.2 指针的定义
4.2.2.1 指针的一般定义方法:
- 类型名,*指针变量名 e.g.
- int i, *p;
-
int *p=&i;
-
int *p,q;
- int* p,q;
- 一些其他数据类型的例子:
- char *cp;
- float *fp;
- double *dp1,dp2;
4.2.2.2 指针的赋值:
- 指针的赋值运算指的是通过赋值运算符向指针变量送一个地址值,向一个指针变量赋值时,送的值必须是地址常量或指针变量,不能是普通的整数(除了赋零以外)。
- “&”,实质是一种一元运算,它必须跟变量,其操作是获取地址
- 输出地址:printf("%p",&i)
- 强制地址转换 p=(int)&i
- 不能给表达式取指针
- *p=10 //对指针p所指向的变量赋值,相当于对变量a赋值
- 指针赋值运算常见的有以下几种形式:
- 把一个普通变量的地址赋给一个具有相同数据类型的指针 >e.g. double x=15, *px; px=&x;
- 把一个已有地址值的指针变量赋给具有相同数据类型的另一个指针变量 >e.g. float a, *px, *py; px = &a; py = px;
- 把一个数组的地址赋给具有相同数据类型的指针。 >e.g.int a[20],*pa; pa = a; (等价于pa = &a[0])
4.2.2.3 指针变量的初始化:
- int a;
-
int *p1=&a;
-
int *p2=p1;
-
指针的初始化
- 一般形式是:< 存储类型 > < 数据类型 > , *< 指针变量名 > = < 地址量 > ;
- 例如: int a,*pa=&a;
4.2.2.4在函数内修改函数外参数的值——修改*p:
示例程序1:
##include <stdio.h>
int main(void)
{
int i=6;
printf("&i=%p\n", &1);
f(&i);
g(i);
return 0;
}
void f(int *p)
{
printf(" p=%p\n", p);
printf("*p=%d\n", *p);
*p = 26;//这句由于是利用地址访问到了函数外的变量,所以可以改变变量的值
}
void g(int k)
{
printf("k=%d\n", k);
}
示例程序2:
##include <stdio.h>
void swap(int *px, int *py);
int main(void)
{
int a=1;
int b=2;
int *pa=&a;
int *pb=&b;
swap(pa,pb);
printf("%d %d",a,b);
return 0;
}
void swap(int *px, int *py){
int t;
t=*px;
*px=*py;
*py=t;
}
- 函数swap2()的实参是指针变量 Da和nb其值分别是变量a和b的地址。在函数swap2()被调用时,将实参pa和pb的值传递给形参Dx和pY。这样,px和 py中分别存放了a和b的地址,px指向a,py指向b.如图8.7( a)所示。由于* px和a代表同一个存储单元,只要在函数中改变*px的值,就改变了该存储单元的内容,如图8.7(b)所示。返回主调函数后,由于a代表的单元的内容发生了变化,a的值就改变了,如图8.7©所示。因此,在函数swap2()中交换*px和*py的值,主调函数中a和b的值也相应交换了,即达到了交换数据的目的。
- 函数swap3()的参数形式与函数swap2()一样,调用时的参数传递过程也相同,如图8.7( a)所示。但在函数swap3()中直接交换了形参指针px和 py的值,如图8.7(d)所示,由于同样的理由,形参px和 py 的改变不会影响实参pa和 pb,因此调用该函数并不能改变主调函数main()中变量a和b值,如图8.7(e)所示。
[例题8-8看不懂orz]
4.2.3 指针的运算
- 指针运算是以指针变量所存放的地址量作为运算量而进行的运算,指针运算的实质就是地址的计算
- 指针运算的种类是有限的,它只能进行赋值运算、算术运算和关系运算。 ![[Pasted image 20240229221647.png]]
4.3 数组与指针:
4.3.1
- 数组的形参a实际上是指针,因此,int a[] 与int *a 实际上等价,s+=a[i] 实际上等于s+=(a+i)[实际上a[i]的存储位置就是a[]平移i个单位]
- 以下四种函数原型是等价的:
- int sum(int *ar, int n);
- int sum(int *, int);
- int sum(int ar[], int n); .
-
int sum(int [], int);
-
数组变量是特殊的指针
- 数组变量本身表达地址,所以int a[ 10]; int*p=a 无需用&取地址
- 但是数组的单元表达的是变量,需要用&取地址 a == &a[0]
- []运算符可以对数组做,也可以对指针做----> p[0]<==> a[0]
- 数组相当于常量指针变量,所以不可以被赋值,因此 b[]=a[]不被允许
- int a[] --- int * const a
4.3.2 指针数组
- 指针数组是指由若干个具有相同存储类型和数据类型的指针变量构成的集合
- 指针数组的一般说明形式:< 存储类型 > < 数据类型 > *< 指针数组名 >[< 大小 >] ;
- 指针数组名表示该指针数组的起始地址
- 声明一个指针数组:double * pa[2] ,a[2][3];
- 把一维数组 a[0] 和 a[1] 的首地址分别赋予指针变量,数组的数组元数 pa[0] 和 pa[1] :
- pa[0]=a[0] ; // 等价 pa[0] = &a[0][0];
- pa[1]=a[1]; // 等价 pa[1] = &a[1][0];
4.4 动态分配
4.4.1 动态内存分配的步骤
- 了解需要多少内存空间。
- 利用C语言提供的动态分配函数来分配所需要的存储空间。
- 使指针指向获得的内存空间,以便用指针在该空间内实施运算或操作。
- 当使用完毕内存后,释放这一空间。
4.4.2 动态内存分配函数
在进行动态存储分配的操作中,C语言提供了一组标准函数,定义在stdlib.h里面。
4.4.2.1 动态存储分配函数 malloc()
- 动态存储分配函数 malloc()
- 函数原型是: void *malloc(unsigned size)
- 功能:在内存的动态存储区中分配一连续空间,其长度为size,若由请成功,则返回指向所分配内存空间的起始地址的指针;若申请内存容间不成功皿返回 NULL(值为0)。
- malloc()的返回值为(void *)类型(这是通用指针的一个重要用途) 在具体使用中,将malloc()的返回值转换为特定指针类型,赋给一个指针。
- 示例程序(动态分配n个整数类型大小的空间):
if((p=(int *)malloc(n*sizeof(int)))==NULL) { printf( "Not able to allocate memory.\n"); exit(1);
- 调用malloc()时,应孩利用sizeof() 计算存储块大小,不要直接写数值。此外,每次动态分配都必须检查是否成功,考虑到意外
- 虽然这里存储块是动态分配的,但它的大小在分配后也是确定的。不要越界用,尤其不能越界赋值,否则可能引起非常严重的错误。
4.4.2.2 计数动态存储分配函数calloc()
- 函数原型是: void *calloc(unsigned n,unsigned size)
- 功能:在内存的动态存储区中分配n个连续空间,每一存储空间的长度为size,并且分配后还把存储块里全部初始化为0。若申请成功,则返回一个指向被分配内存空间的起始地址的指针;若申请内存空间不成功,则返回NULL(0)。
- malloc()对所分配的存储块不做任何事情,calloc()对整个区域进行初始化。
4.4.2.3 动态存储释放函数free()
- 函数原型是: void free(void *ptr)
- 功能:释放由动态存储分配函数申请到的整块内存空间,ptr为指向要释放空间的首地址。如果 ptr的值是空指针,则free什么都不做。该函数无返回值。
4.4.2.4 分配调整函数 realloc()
- 函数原型是: void *realloc(void *ptr,unsigned size)
- 功能:更改以前的存储分配。ptr必须是以前通过动态存储分配得到的指针。参数size为现在需要的容间大小。如果分配失败,返回NULL,同时原来ptr 指向存储块的内容不变。如果成功,返回一片能存放大小为size的区块,并保证该块的内容与原块的一致。如果size小于原块的大小,则内容为原块前size范围内的数据;如果新块更大,则原有数据存在新块的前一部分。如果分配成功,原有的存储块内容就可能改变了,因此不允许再用*ptr来访问。
4.5 注意事项:
- ”*“只是表示该变量是指针变量,不是运算符
- 把一个变量的地址给指针变量之前,必须先定义这个变量,因为只有定义之后才会分配地址
- 不可以直接定义初值,但0可以:
- int *p= 1000 X
- int *p=0 √ =NULL
-
一旦两个指针指向同一个位置,改变另一个指针,原指针也会变化 e.g.
##include<stdio.h> int main() { int m=1; int *p=&m; int *q; q=p; *q=2; printf("%d",*p); return 0; }
-
以上程序的返回值是2.
-
对于普通变量而言,存放的是变量的值,但是对于指针变量而言,存放的是变量的地址
- 函数:
- void f(int *p)
-
int i=0; f(&i)
-
*是一个单目运算符,用来访问指针的值所表示的地址上的变量
- *可以做右值也可以做左值
- int k= *p;
- *p= k+1;
5 字符串
5.1 字符串的初始化
- e.g. char word[]={'H','e','l','l','o','!','\0'};
word[0] | H |
---|---|
word[1] | e |
word[2] | l |
word[3] | l |
word[4] | o |
word[5] | ! |
word[6] | \0 |
5.2 字符串的定义
5.2.1 Basic
- 以0 (整数0)结尾的一串字符
- 0或'\0’是一样的,但是和'0’不同
- 0标志字符串的结束,但它不是字符串的一部分
- 计算字符串长度的时候不包含这个0
- 字符串以数组的形式存在,以数组或指针的形式访问
-
更多的是以指针的形式
-
char *str ="HELLO”
- char word[]="HELLO“
- char line[10]="HELLO"
- "HELLO"->长度是6
- 不能用运算符对字符串进行运算
char* s = "Hello,world! "; - s是一个指针,初始化为指向一个字符串常量 - 由于这个常量所在的地方,所以实际上s是const char* s ,但是由于历史的原因,编译器接受不带const的写法 - 但是试图对s所指的字符串做写入会导致严重的后果
- 当我定义char s3[]的时候,我就可以对s3[0]进行修改了
5.2.2 char* 是字符串?
- 字符串可以表达为char*\的形式
- char*\不一定是字符串
- 本意是指向字符的指针,可能指向的是字符的数组(就像int*—样)
- 只有它所指的字符数组有结尾的0,才能说它所指的是字符串
5.3 字符串输入输出(printf和scanf的使用)
5.3.1 scanf的使用
- 防止越界 在“%”和“s"间加一个数字
- scanf("%7s",word)
- 不是说一定要读满七个字符,而是说最多就读七个字符
- 假设我第一次输入123|12345678 out: 123 1234567
-
若输入12345678 out:没有第二次输入的机会:1234567|8
-
错误示例:
- char *string;
- scanf(“%s" , string);
- 原因:以为char*是字符串类型,定义了一个字符串类型的变量string就可以直接使用了
5.3.2 定义空串
- char buffer[100]="";
- 这是一个空的字符串,buffer[0] ==
- char buffer[]="" 则buffer字符串的长度是1
5.3.3 字符串函数
- 需要##include
- strlen:
- char* buffer="Hello!"
- 此时strlen(buffer)是5,sizeof(buffer)是6
- strcmp
- int strcmp(const char *s1 , const char *s2);
- 比较两个字符串,返回:
- 0:s1==\s2
- 1: s1>s2(返回差值)
- -1 s1\<\s2(返回差值)
- 不能直接比较s1 s2会直接报错(数组不能直接比较)
- strcpy
- char * strcpy(char *restrict dst, const char *restrict src);
- 把src的字符串拷贝到dst
- restrict表明src和dst不重叠(C99)
- 返回dst
- 为了能链起代码来
- strcat
- char * strcat(char *restrict s1, const char *restrict s2);
- 把s2拷贝到s1的后面,接成一个长的字符串
- 返回s1
- s1必须具有足够的空间
[连接也是一种拷贝:只不过拷贝是从[0]开始,而连接是从strlen(数组)开始]
- strncmp→仅比较前n个字符
- strrchr:从右边开始找 strchr:从左边开始找
- char * strchr(const char *s, int c);
- char * strrchr(const char *s, int c);
- 返回NULL表示没有找到