Chapter4 - Swicth
if else语句可能出现少打花括号的问题
逻辑运算 &&、|| 的短路特性:
(i != 0)&&(j / i >0)保证了除0运算不会发生
switch
若所有的case常量都不能与表达式的值相匹配,那么就执行default后面的语句。通常用于处理不在合理区间内的非法数据。
程序执行到switch语句时,先计算表达式的值,然后自上而下寻找与该值匹配的case常量,找到后则按顺序执行此case后的所有语句,而不再进行判断,直到遇break语句或右花括号}为止。
case后面的表达式不能是含有变量的关系表达式和逻辑表达式,它不能像if语句那样起到判断表达式真假的作用。
数值溢出、截断
如 int转short
SoftwareTesting、浮点数技巧
Ex:判断三角形的类型
10,10,14.14(10
2
\sqrt{2}
2的近似值),不能直接用“==”判断,
CSAPP中也讲过,浮点数比较相等通常使用作差法(fabs<设定的 误差值)
Ex:浮点数判0
#include <math.h>
#define EPS 1e-6
if (fabs(disc) <= EPS) /* 判别式等于0时,... */
一旦遇到除法,无论整形还是浮点,必须除数判零
Chapter6-7 ModuleDesign & Function
v尾递归:不返回,一般是加参数
Chapter9 Pointer——Intro
1.指针变量未初始化就直接使用
(定义指针变量可以不初始化,但使用时一定要)
void swap(int *x,int *x)
{
int *pTemp;
*pTemp = *x;
*x = *y;
*y = *pTemp;
}
//本函数不可以实现交换
2.即使是数组元素,swap也必须是传地址
3.函数指针
float integral(float(*f)(float),float a,float b)
{
float sum = 0;
for(int i = 0;i < N;i ++)
{
sum += f(i/N)*(b-a)/N;
}
return sum;
}
2.空指针和void指针
int *p = NULL;//代表“无效指针”(指向为空)
void *q;//代表“可指向任意类型”的指针
关于void指针,典型的例子就是
void *malloc(size_t size)
Chapter10 String
1.字符指针的两种写法-指向常量存储区和指向runtime数组
#case 1 字符串存储常量存储区,用指针指向
char *s = {"abcd"};
char *a=s;//将保存在常量存储区的“Hello”首地址赋值给a(a指向该静态存储区)
*a = 'c';//错误语句!!不能对常量修改,指针仅仅是指向
#case 2 字符串存储在数组中
char s[] = {"abcd"};
char *a=s;//a指向该数组,等价于 a = &s[0]
*a = 'c';//完全正确
2.(字符)指针做参数,按传值
1.在c语言中实参和形参之间的数据传输是单向的“值传递”方式,也就是实参可以影响形参,而形参不能影响实参。
指针变量作为参数也不例外,但是可以改变实参指针变量所指向的变量的值。
#include <stdio.h>
unsigned MyStrlen(char *p)//char *p做参数,指针的值做参数,和swap(a,b)原理相同
{
unsigned int len;
len = 0;
for(;*p != '\0';p++)//p作为参数,却执行了自增操作
{
len ++;
}
return len;
}
int main()
{
char *p = "abcd";
char *ptr = p;
printf("%d",MyStrlen(p));
//也可写成如下
char p[] = "abcd";
printf("%d",MyStrlen(p));
}
上例中的ptr指针,指针可以自增,但
2.再看一个例子
void inversePointer(char *str)//字符指针做参数
{
char s[N];
char *t = s;
strcpy(t,str);
while(*(t+1) != '\0')//points at the last char
{
t ++;
}
while(*(str+1) != '\0')
{
*str = *t;
str ++;
t --;
}
}
本例中,str做参数,在函数内部运算地花里胡哨,但出了函数,还是恢复指向字符串的首地址,因为在函数中只是临时拷贝的地址用于做参数并运算
3.在看一个例子(忽略了数组名做参数的本质 (传地址),冗余定义变量)
void MyStrcat(char dst[],char src[])
{
char *p = dst,*q = src;//实际上可以直接用dst、src进行运算,这是参数的本质
while(*p != '\0')
{
p++;
}
while(*q != '\0')
{
*p = *q;
p++;
q++;
}
*p = '\0';
}
修改如下
void MyStrcat(char dst[],char src[])
{
//MyStrcat的形参是数组,但实际上传的实际参数是一个地址,他是按指针来处理的,所以可以这么用(自增),
while(*dst != '\0')
{
dst++;
}
while(*src != '\0')
{
*dst = *src;
dst++;
src++;
}
*dst = '\0';
}
3.字符串数组与指针数组的对比
用数组名法定义字符串,不可以用数组名进行“自增”等运算
//正确形式
char *p = "abcd";
p ++;
//错误形式
char p[] = "abcd";
p ++;//直接报错,因为p这里是数组名
//错误形式更正如下
char str[] = "abcd";
char *p = str;
*(p+2) = 'a';//可对字符串进行修改
4.字符串target造副本t
(实现删除某字符、插入空格 10.5、10.6)
char str[N];
char *t=str;
strcpy(t,target);
5.字符串一定要’\0’结尾!
特别是在Mystrcat、Mystrcpy等自定义函数中
Chapter11 Pointer——Advanced
note:指针的解引用就是*这个符号,从该地址取出值
1.二维数组和指针数组
二维数组 int (*a)[N]
int (*a)[10] 指向一个整型的一维数组,这个一维数组的长度是10,也可以说是a的步长。也就是说执行a+1时,a要跨过10个整型数据的长度;
int a[10] 这个是最基本的数组,不多说了
5单选(1分)
char (*p)[10];该语句定义了一个
A.指向长度为10的字符串的指针变量p
B.有10个元素的指针数组p,每个元素存放一个字符串
C.有10个元素的指针数组p,每个元素可以指向一个字符串
D.指向含有10个元素的一维字符型数组的指针变量p
指针数组 int *a[N]
指针数组怎么做参数?
指针数组做参数
以string为例:
#include <stdio.h>
void Print(char *arr[], int len);
int main()
{
char *pArray[] = {"How","are","you"};
int num = sizeof(pArray) / sizeof(char);//此处应÷sizeof(char *),因为指针数组里面不是存的字符串,是固定长度的地址(本机为64位)
printf("Total string numbers = %d\n", num);
Print(pArray, num);
return 0;
}
void Print(char *arr[], int len)
{
int i;
for (i=0; i<len; i++)
{
printf("%s ", arr[i]);
}
printf("\n");
}
指针数组做比如“自增运算”?
int *p[10];
p ++;
2.二维数组列指针
int a[3][3] = { 1,2,3,4,5,6,7,8,9 } ;
int s = 0 ;
for( int *p = a[0] ; p < a[0] + 9 ; p++ )
{
s += *p ;
}
printf("%d\n" , s ) ;
3.二维数组传参数
注意 函数定义时参数写法、调用时参数传法
方法一:二维数组直接做参数
必须指定列数
方法二:二维数组行指针做参数 int (*p)[N]
本质上,这是二维到二维的拷贝传递
参数拷贝传递方法:
int (*p)[N];
int a[N][N] = {...};
p = a;
printf("%d",p[0][1]);//完全正确!!
行指针二维数组--打印一个元素的值:(标准答案)
printf("%d ",*(*(a+i)+j));//*(a+i)+j即是元素的地址,swap就用它
而不是*(a+i)[j]//没有这种用法,目前是没用碰到,直接使用的话,部分是可以正常打印的,但会出现越界(不打印数组元素而是打印地址),就很奇怪,暂且只记住标准用法
03-16 修改为(*(a+i))[j]就对了(改成a[i][j]同样正确!!)
方法三: *a是a[0][0]的地址
本质上,这是二维到一维的拷贝传递
重要等式
int *s = a[0];
则有:
a[0] == &a[0][0] == *a
1、二维数组直接做参数
void Transpose(int a[][N],int n)//必须指定列数
Tansepose(a,n);//调用
2、二维数组行指针做参数
void Transpose(int (*a)[N],int n)//必须指定列数,其实就和第一种写法完全一样
{
int i,j;
for..
for..
swap(*(a+i)+j,*(a+j)+i);
}
Tansepose(a,n);//调用
3、二维数组的“列指针”做实参
void Transpose(int *a,int n);
调用方法如下
int a[3][3] = { 1,2,3,4,5,6,7,8,9 } ;
int *s = a[0];
//等价于int *s = *a;
//等价于int *s = &a[0][0];
Transpose(s,n);//实际上就是把地址&a[0][0]传进去
做函数参数时一定要指定列大小
例题1:
单选(1分)
设有以下定义:
int a[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int (*ptr)[3] = a;
int *p = a[0];
则以下能够正确表示数组元素a[1][2]的表达式是
A.((ptr + 1) + 2)
B.(ptr + 1) + 2
C.((ptr + 1) + 2)
D.(*(p + 5))
//我的分析:D选项多了一个*,因为p指向a[0],而a[0]&a[0][0]*a
例题2:
有以下程序段,则*(p[0]+1)所代表的数组元素是:
#include <stdio.h>
int main()
{
int a[3][2]={1,2,3,4,5,6,},*p[3];
p[0]=a[1];
…
return 0;
}
A.a[1][2]
B.a[1][1]
C.a[1][0]
D.a[0][1]
例题3:花式指二维数组
12单选(1分)
在以下程序段中的空白处填写适当的表达式或语句,使程序能正确引用c数组元素。
#include <stdio.h>
int main()
{
int c[4][5],(*p)[5],i,j,d=0;
for(i=0;i<4;i++)
{
for(j=0;j<5;j++)
{
c[i][j]=d;
d++;
printf("%4d",c[i][j]);
}
printf("\n");
}
p=c;
printf("%d,%d\n",____________);
return 0;
}
A.p+1,c[0][1]
B.(p[0]+2),c[0][2]
C.(p+1)+3,c[1][3]
D.*(p+3),c[0][3]
例题4单选(1分)
若二维数组a有m行n列,则下面能够正确引用元素a[i][j]的为
得分/总分
A.((a+i)+j)
B.(a+jn+i)
C.(a+in+j)
D.*(*a+i)+j
正确答案:A你错选为C
### 数组寻址,一定注意行列长度是开始就给定的
```c
void Total(int *score, int sum[], float aver[], int m, int n)
{
int i, j;
for (i=0; i<m; i++)
{
sum[i] = 0;
for (j=0; j<n; j++)
{
sum[i] = sum[i] + *(score + i * COURSE + j);//这里的列长度应该用数组初始化时的,而不是用户输入的
}
aver[i] = (float) sum[i] / n;
}
}
Chapter12 Struct
1.struct定义
1.必须加分号
2.背诵定义的格式:
例题:
2单选(1分)
以下选项中不能正确把cl定义成结构体变量的是
得分/总分
A.
typedef struct
{
int red;
int green;
int blue;
} COLOR;
COLOR cl;
B.
struct
{
int red;
int green;
int blue;
} cl;
0.00/1.00
C.
struct color cl
{
int red;
int green;
int blue;
}
D.
struct color
{
int red;
int green;
int blue;
} cl;
正确答案:C你错选为B
2.结构体传参
1.按值传递
void print(Darray aPtr)
2.按引用传
void grow(Darray *aPtr,int len)
动态内存分配
动态数组基本操作
typedef struct
{
int *array;//动态数组的地址
int length;//数组长度
}Darray;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJtsICSk-1616679333969)(/assets/Darray_iy69r6s3k.png)]
Darray create(int len)
{
Darray a;
a.array = (int *)malloc(sizeof(int)*n);
...
...
}
void grow(Darray *aPtr,int len)//增长到len长度
{
//思路就是开一个新数组
int *p = (int *)malloc(sizeof(int)*n);
...
//一定要free
free(aPtr->array);
}
动态数组经典错误
1. 未初始化直接使用
如果使用malloc,最好用memset()进行清零操作,calloc()能自动将分配的内存初始化为0
2. 操作越界
使用strcpy()、gets()、memcpy()
3. free§
free§之后,若要再使用p,需要先p=NULL,否则是野指针[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LXqZ1Gh0-1616679333971)(/assets/多指针指向同一块内存_cr0vmck62.png)]
解决对策:
1尽量把free集中在函数出口
2若不能,free后立即置NULL
4. 从函数中返回局部变量的地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aa7Lafug-1616679333974)(/assets/函数中返回地址_d0yah412b.png)]
5. 使用pointer不初始化或分配内存
错误示范://把注释部分去除即可
#include <stdio.h>
void GETINPUT(char *s)
{
scanf("%s",s);
puts(s);
}
int main()
{
char *p = NULL,
//char str[80];
//p = str;
GETINPUT(p);
puts(p);
}
正确示例1(在函数中分配内存)
#include <stdio.h>
void GETINPUT(char *s)
{
s = (char *)malloc(80);
scanf("%s",s);
puts(s);
}
int main()
{
char *p = NULL;
GETINPUT(p);
puts(p);
}
正确示例2
#include <stdio.h>
char* GETINPUT()
{
char *s;
s = (char *)malloc(80);
scanf("%s",s);
puts(s);
return s;
}
int main()
{
char *p = NULL;
p = GETINPUT();
puts(p);
}
未回收内存
- 忘记free
必须free! - 乱指
char *p,*q;
p = malloc..
q = malloc..
q = p;//结果q原来指向的内存块变成了“孤魂野鬼”
- 程序结束后,内存都被回收了吗?