#include <stdio.h>
main(void){
int x=1,y=2,z=3;
if(x<y)
if(y>z) printf("%d",++z);
else printf("%d",++y);
printf("%d\n",x++);
}
相当于
#include <stdio.h>
main(void){
int x=1,y=2,z=3;
if(x<y){
if(y>z) printf("%d",++z); // if-else算是一条语句。else必须且只能与它最近的那个未配对的、可见的if配对。
else printf("%d",++y);
}
printf("%d\n",x++);
}
但是:
int i = 4;
if(x--) printf("%d\n",x);
输出结果仍为3,虽然if是一条语句,但也有执行顺序先后之分。
ASCLL码中,65 -> A,90 -> Z,97 -> a,122 -> z,48 -> 0。
大的在前,小的在后。
A+1==B;
A+25==Z; (有26个字母)
double a,b;
int i;
W%((int)a+b); // 错误,b未被强转
x = x + 8 / ++n;
注意是没有括号的,所以先自加一再乘除后加减。
定义char类型时,若用ASCII码赋值则不需要加单引号,直接赋值字母则需要加。
只有整形在允许范围内能精确无误地表示,实型数据则会有误差,如1/3在float的表示范围内却不能精确表示。
scanf可定义为标识符,因为他不是关键字,scanf();是一个函数而已,我们只是调用函数,
同理printf也可以定义为标识符。
C无逻辑类型(true/false)和集合类型。
-sub不能作标识符,因为含有运算符了。
求余运算、位运算和要求两边都必须为整形(短的长的都可)。
但除法不限制。
实型、常量是不能进行自增操作的。
65535是无符号短整型的表示范围。
简单记就是:
{ [ ] () . -> } > 按位取反~ > 自增自减(前) > 取值取址 > 非! > 算术运算符 > 二进制左右移运算(<< >>) > 关系运算符 > 按位运算符 > 逻辑运算符 > 赋值运算符 > 自增自减(后)
{ [ ] () . -> } ~ ++ *& ! 算移关 ‘按位’逻赋 ++
横的~ 有点突起++ 圆块*& 竖!算你(移)过关 安慰洛夫
加减乘除的优先级高于 < > ,因而x+1>10能准确判断。
<<的优先级高于^,所以a^b<<2先执行b<<2再执行a^b。
+的优先级高于+=,所以s += i + 1; 是右边先执行。
whlie(z-- > 0 && ++y > 5) 中,也是先判断z是否比0大再减减的。要注意的是,即使判断z并不大于0,循环体不执行时,z也会执行自减一的操作。
= 与 != 如果在同一个括号内,那么 != 先执行。(赋值运算符的右结合)
取值*比乘号*的优先级更高,所以*p*(*p)是先取值后乘。
! 的优先级比 && 和 || 更高,因此 !(y-x)<-10 不等价于 (y-x)>=-10 ,因为 ! 会先执行。
注意:因为 自增自减 和 取值运算符 是同一优先级(但都没有括号的优先级高),但他们的结合方向是从右到左,但同时又不能忽略自增自减的位置带来的运算顺序的改变(前增和后增),有个方法,就是把先结合(注意不是先运算)的那一个用括号先括起来,然后再进行判断。
如:
*pa++ ,先把右结合括起来,即 *(pa++) ,因为是后加加,所以是先取值再地址加一(因为括起来了,++是对pa进行操作的)。
*++pa ,先把右结合括起来,即 *(++pa) ,因为是前加加,所以是先地址自加一然后再取值(因为括起来了,++是对pa进行操作的)。
(*p)++ ,这个就不用把右结合先括起来了,因为括号的优先级是最大的,所以先取值,再指向的变量中值自加一。
++(*p) ,同上,也是先取值再指向的变量中值自加一。 (只有在赋值时或输出时两者才会有区别)
注意,*(p)++特殊,*(p)++是取值然后地址自加一。
++*(p)是取值然后值加一,址不变。
(括号优先级最高,*靠近()所以都是先取值,然后如果++靠近p就是地址加一,靠近*就是值加一)
char a = ’\n’; 可以
m=j==5; 合法。
先判断j与5相等不,相等结果为1,否则为0,在把0或1赋值给m。(=为右结合)
x+1=x+1;
该语句不合法,常量不能被赋值。
#include <stdio.h>
main{
int a=1,b=2,m=0,n=0,k;
k=(n=b>a)ll(m=a<b);
printf("%d,%d\n”,k,m); // 输出1,0,因为||左边为1(true)了右边就不会执行了
}
#include <stdio.h>
main(void){
int x=1,y=2;
printf("%d\n",(x,y)); // 输出2,因为逗号运算符是从左到右一条一条表达式进行运算,然后返回最终结果是最右边的表达式的值
}
int x;
printf("%d\n",(x=3*5,x*4,x+5)); // 输出结果为20,因为x*4并没有赋值给x。
int x=1,y=2,z=3;
x=y=z+2; // 允许,C中允许连续赋值,运算后,z为3,x和y为5。
//前提是x,y已经定义
int i = 023; // 把八进制的023赋值给i,二进制为0bxxx,十六进制为0x。
double x=1;
printf("%.1f\n",x+3/2); // 先除法运算输出2.0,因为3/2是int类型,是1!不是1.5!
printf是个输出函数,其运算顺序在不同的编译环境下是不同的,一定注意这点,具体说明如下:
int i=1; printf("%d,%d\n",(i++,i++),i); // 2,3
i = 1; printf("%d,%d\n",(i++,++i),i); // 3,3
i = 1; printf("%d,%d\n",i,(i++,i++)); // 3,2 //括号先执行,但括号内第二个i++是先结束括号运算符了返回2了后再自加一
i = 1; printf("%d,%d\n",i,(i++,++i)); // 3,3
i=1; printf("%d,%d\n",i,i++); // 2,1 //输出语句中,若处于同一优先级,则表达式优先执行
i=1; printf("%d,%d\n",i,++i); // 2,2
i=1; printf("%d,%d\n",i++,i); // 1,2
i=1; printf("%d,%d\n",++i,i); // 2,2
i=1; printf("%d,%d\n",i=i+1,i); // 2,2 //输出语句中,若处于同一优先级,则表达式优先执行
i=1; printf("%d,%d\n",i,i=i+1); // 2,2
但单个输出位置的++或--的前后不同还是严格区分的。
输出语句内依旧是括号内先执行。
函数类型默认为int,如main(){} 等于int main(){} 。
如果定义一个char数组,并用gets语句存入数据,则存入的是char类型的数字。
如果char c = 3; 这是用ASCLL码来赋字符值,c转为int类型就是3。
如果char c = ‘3’ 则c转为int类型是51。(同存入char数组)
scanf(“%d%d”,&a,&b); 在输入数据时,必须以空格或TAB键或回车隔开,否则识别不了。尽管scanf中两个%d间没有空格隔开。
switch后面的表达式要与case常量表达式类型一致,类型可为字符型!!!(字符型也是常量,如’A’)
也可为常量表达式,如case 1+2;等价于case 3;
case后面不能是变量,const修饰的变量也不行!
for循环的循环条件初始化部分仅会运行一次,别忘了。
break 只对选择语句的switch和循环语句有效,也可作用于函数。
continue只会使离他最近的一个循环进入下一次循环,对if等语句不起作用,别忘了。
虽然B在判断w等于0后不执行代码块了,但w还是会执行自减操作,但别看漏了后面还有个w++;
正确答案应该是C,因为C至少执行一次,与其他不符合。
int main()
{ // B选项'N'后面少了一个括号)
char ch;
while( (ch = getchar()) != 'N' )
printf("%c",ch);
char ch;
while( ch = getchar() != 'N' ) // 若 ch = getchar() 没有括号,那么 != 会先执行(赋值运算符的右结合性质),返回一个 int 类型,则输出应为 %d
printf("%d",ch);
// C D 因为判断符并不在while的循环条件判断括号内,而char类型的字母转为int类型均为非 0 ,所以错
}
函数声明中的变量的名字可与函数定义中的名字不同,但类型要一致。
形参可以使用的储存类说明符与局部变量完全相同。
错。形参为局部变量,不能定义为static。
实参可以是常量、变量或表达式等。
做选择题的时候,注意函数有没有返回值、传的参数是不是指针,对main函数有无影响。
静态变量在函数内被首次定义后,之后再次调用该函数时,静态变量不会再次被定义,直接执行下一个语句。
同一个源文件中,外部变量和局部变量同名,则在局部变量的作用域内,全局变量被屏蔽。
main(){
{int a = 5;
}
这里访问不了a,出了大括号就不认识了。
}
数组名虽然是指针常量,但他只是不能被赋值,但还是可以加某个常数然后进行一些操作的。
#include <stdio.h>
void sort(int a[],int n){
int i,j,t;
for(i=0;i<n-1;i++){
for(j=i+1;j<n;j++){
if(a[i]<a[j]){
t= a[i];
a[i]=a[j];
a[j]=t;
}
}
}
}
main(){
int aa[10]={1,2,3,4,5,6,7,8,9,10},i;
sort ((aa+2),5); // 这里aa指向的是数组第一个元素,加2后相当于把第三个元素的地址给了函数sort,所以a[]储存的是{3,4,5,6,7,8,9,10}
for(i=0;i<10;i++){
printf("%d",aa[i]);
printf("\n");
}
}
static int a,b;
// 此处的a和b均被static所修饰
getchar()函数是从终端(缓冲区)输入一个字符,而不一定是从磁盘(内存永久储存区)读入字符。
所以,
getchar函数用于从磁盘文件读入字符 --->(错误)
a = (double)c/d;
是先将c强转为double类型再进行除法运算的。(括号的优先级最高)
做宏定义的题注意宏定义中有无括号。
A正确!!!不放在可执行部分就不叫赋值语句了(注释)。
scanf和printf是c的库函数。
输出数据的实际精度并不完全取决于格式控制中的域宽和小数的域宽,而是取决于数据在计算机内的存储精度。通常系统只能保证float类型有7位有效数字,double类型有15位有效数字。若你指定的域宽和小数的域宽超过相应类型数据的有效数字,输出的多余数字是没有意义的,只是系统用来填充域宽而已。(不是你的域宽越大,数据就会越精准,会受到数据类型的限制)
printf输出的数据是默认右对齐(输出宽度足够的条件下),
如 printf("%10d",k);
输出“ 10”。
0的阶乘是1。
1不是素数。(除了1和自身无其他公因数,而1自身就为1,违背了法则)
最小的素数是2。
完数是指不包括自身的因子之和等于自身的数,6是,1不是。
<math.h>中,sqrt()是计算某个数的平方根。形参实参均为一个double类型。
abs()是求整数x的绝对值。
fabs()是求双精度实数x的绝对值。
<ctype.h>中,isupper()是判断是否为大写字母的,是则返回1,不是则返回0。
is supper:是超级,是大写,是返回1(ture),否则返回0(false)。
toupper()是将小写字母转为大写字母的。
to supper:成为超级,成为大写。
<string.h>中,strlen()是求字符串的长度的,不包括\0。
<stdlib.h>中,rand()是产生一个随机数。
还有字符串那一章的常用字符函数。
memcpy函数是C/C++语言中的一个用于内存数据复制的函数,声明在 string.h 中(C++是 cstring)。
其原型是:void *memcpy(void *destin, void *source, unsigned n);
作用是:以source指向的地址为起点,将连续的n个字节数据,复制到以destin指向的地址为起点的内存中。(也是后面的复制给前面的,类似于strcpy();)
函数有三个参数,第一个是目标地址,第二个是源地址,第三个是数据长度。
使用memcpy函数时,需要注意:
1、数据长度(第三个参数)的单位是字节(1byte = 8bit)。
2、注意该函数有一个返回值,类型是void*,是一个指向destin的指针。
strstr ()函数:
strstr (str1,str2)函数用于判断字符串str2是否是str1的子串。如果是,则返回str2在str1中首次出现的地址:否则,返回NULL。
库函数中 strstr ()函数的原型是 char * strstr ( const char * str1, const char * str2 ),包含在头文件<string.h>中。
100则错了。
A选项打错了,应该是signed,即默认的,有正有负的。
但D在C中是没有的,不存在的。
注意忽略正负号的要求
注意题干已经定义了常量PI。
a[1]是一个列指针,指向第2列的第一个元素,即等价于a[1][0]。
p是一个指针数组。
把a[1]这个指向第二个一维数组的一维指针赋值给p[0];
该二维数组在内存中的排列方式如下:
a[0][0]、a[0][1]、a[1][0]、a[1][1]、a[2][0]、a[2][1]。
则p[0]+1即指向a[1][1]。
永远要记住,数组是一片连续的内存。第一次循环时,把aa[2]传给a[0],所以a[0]=aa[2],a[1]=aa[3]。 答案A。
字符数组或字符串常量其后面都有\0结尾。
char a[][10] = {{abc},{def}{ghijk}} 中,a数组的第一维是3(一个字符串就占一个char数组),第二维是10,剩的元素位默认赋上int类型的0值。
无论什么数组,只要是没手动赋值的元素位,都会被赋上int类型的0值。
main()函数的带参的形式:
main(int argc,char *argv[])
{
.....
}
从函数参数的形式上看,包含一个整型和一个指针数组。agrc为参数的个数,argv[0]为自身运行程序名(不含.exe),argv[1]指向第一个参数、argv[2]指向第二个参数、等等。
对main()函数,既然不能由其它函数调用和传递参数,就只能由系统在启动运行时传递参数了。在操作系统环境下,一条完整的运行命令应包括两部分:命令与相应的参数。 其格式为:
命令参数1参数2....参数n
此格式也称为命令行。命令行中的命令就是可执行文件的文件名,其后所跟参数需用空格分隔,并为对命令的进一步补充,也即是传递给main()函数的参数。 命令行与main()函数的参数存在如下的关系:
设命令行为:program str1 str2 str3 str4 str5
其中program为文件名,也就是一个由program.c经编译、链接后生成的可执行文件program.exe,其后各跟5个参数。对main() 函数来说,它的参数argc记录了命令行中命令与参数的个数,共6个,指针数组的大小由参数argc的值决定,即为char*argv[6]。
数组的各指针分别指向一个字符串。应当引起注意的是接收到的指针数组的各指针是从命令行的开始接收的,首先接收到的是命令,其后才是参数。
main()函数还有第三个参数env,env: 安符串数组。(暂不学)
应该提醒的是: 传送main() 函数的命令行参数的最大长度为128 个字符 (包
括参数间的空格), 这是由DOS 限制的。
不是错在它后加加,而是错在数组名是常量不能自增自减操作。
被调函数改变原函数的变量需要传原变量的指针,
而改变原函数的指针需要传原指针的指针,即二级指针。
// str与str[0]有区别吗
str是第一个元素的地址即&pstr[0],也是指针常量,并不是它保存的内容str[0]。
// 对于scanf(“%s”,);与printf(“%s”,);来说,后面均是要加字符串的地址值。
char str[10]=“abcdefg” , *p; p=str;
即:字符数组名(str)、字符指针(p)、字符数组元素的地址(&str[0])(如果是&str[1]的话则从第二个字符开始输出)
纵观全局,看清每一个变量有没有赋初值。
判断有无读取完字符的循环条件除了运用到strlen函数外,还可以用xx[i]!=’\0’; 因为ASCLL码为0的字符为NULL,即使字符串含NULL,在读取时也是一个一个字符来读取,不会读取出\0的情况,就算字符串含0,也是char类型的0。
另外,if中的continue答案是不写的,只写一个分号,表示啥也不干,两个应该都是可以的,但可以按答案来写,如果考试是人工改卷的话。
定义包括了声明和赋值,选B。
数组元素可以用表达式来表示,如B的2*3。用变量也可以,如{a*3}。
在进行字符数组的输入时要注意,%c是会识别 空格 和 换行 并将其输入的,但%s遇到 空格 或 换行 都会停止输入。(%s不能录入空格)
别忘了二维指针的定义要两个*,如int a=10; int *p1=&a; int **p2=&p1;
运算级别高的先确定该变量的性质:
指针数组:
int *n[];
由于 [] 的运算级别要比 * 高,因此这里的声明变量应当按照从右往左的顺序,即先确定n是一个数组, 然后这个数组内的元素是指针类型的,指针指向的数据类型为 int。因此这里声明的是一个int类型的指针数组。
注意,*n[]存放的是一个一维指针(一维数组名),**n[]存放的是二级指针(二级数组名)。不是*n[][],因为n不是二维数组。
存放后,指针数组的元素就可以看作是存进去的一维或二维指针来使用。
如: int a=10;
int b[][2]={30,50},*p1=&a,**p2=&p1;
int **p[]={p2};
a=**p[0]*5;
printf("%d",a);
代码示例:
(一)
#include <stdio.h>
int main(void)
{
char tBooks[] = {'A'};
char *p[4] = {&tBooks[0]};
//tBooks[0]是一个具体元素,&tBooks[0]是一个一级指针
printf("请打印:\n%c\n",*p[0]);
// p[0]保存了&tBooks[0],即保存了具体元素的地址,所以p[0]是一个一级指针,*p[0]就是'A'
}
(二)
#include <stdio.h>
int main(){
char *tBooks[] = {"《数据结构》","《计算机组成原理》","《C语言程序设计》","《计算机网络》","《哆啦A梦》"};
char **pt=&tBooks[4];
char **p[4] = {&tBooks[4]};
printf("%s\n",tBooks[4]); //打印出《哆啦A梦》
printf("请打印:%s\n",*pt); //打印出《哆啦A梦》
//tBooks[0]是一个一级指针,pt是保存其变量的二级指针,那么*pt就是tBooks[0]
printf("请打印:%s\n",*p[0]); //打印出《哆啦A梦》
/*
tBooks[4]是一个一级指针,&tBooks[4]是其地址也是一个二级指针,
&tBooks[4]保存于p[0],p[0]==&tBooks[4],则p[0]就是二级指针,*p[0]就是指向字符串的一级指针,可直接%s输出
*/
}
数组指针:
int (*n)[];
由于 () 的优先级要比 [] 高,因此这里的声明顺序从左到右,即先确定n是一个指针,然后它指向的数据类型是数组类型的,可以是一维数组,也可以是二维数组。且数组内的数据类型是 int 类型的。
假设定义 int (*p2)[5] = { 0, 0, 0, 0, 0}; 那么p2就指向了这个包含5个int类型的数组了。
例如:
#include <stdio.h>
int main()
{
int temp[5] = {1, 2, 3, 4, 5};
int (*p)[5] = &temp; // 从此,(*p)等价于temp
int i;
for(i = 0; i < 5; i++)
{
printf("%d\n", *(*p + i));
//或者 printf("%d\n", (*p)[i]);
}
return 0;
}
1)第一行定义了含有5个int类型的数组temp;
2)第二行就定义了一个指向含有5个int类型数组的指针p,并把temp数组的首 地址给了指针p。
注意:这里为什么不直接用 int (*p)[5] = temp;呢?
这是因为虽然temp和&temp的值都是相同的,但它们的意义却是不相同的:
*** temp指的是这个数组的 第一个元素 的首地址。
*** &temp 指的是这 整个数组 的首地址。
3)for循环是想把数组temp里面的数字都打出来。里面为什么用*(*p + i)呢?
p所指的是数组temp的地址,
*p+i 所指的是它在数组中的位置,
*(*p + i)就表示相应位置上的值了。
4)如果想指向二维数组的temp,则数组指针的定义要变成(*p)[][x]=&temp;
x为要标明的第二维长度。使用方法:(*p)[i][j]。
5)注意 定义二维数组的行指针 和 定义二维数组的数组指针 是不同的。
如有二维数组a[4][5];
该二维数组的行指针:
int (*pa)[5]; // 定义一个行指针,指向二维数组的每一行,一行(一个一维数组 / 二维数组的第二维)有5给元素
pa=a;
// 行指针与指向一维数组的数组指针一样,因为行指针就是指向一维数组的数组指针,他俩都是指向一维数组
该二维数组的数组指针:(*pa)[i][j];
行指针的使用方法:pa[i][j]; 数组指针的使用方法:(*pa)[i][j];
行指针自加一则变化一行,其实理解起来挺简单的,因为现在pa==a,而pa+i是指向下标为i的那一行的第一个元素。
-------------------------- *************** --------------------------
// 不能*p[1],因为[]会先执行,但p不是数组,会报错
***** 注意:因为数组名是一个指针常量,是一个常量,不是(一级)指针变量,对其取址并赋值给数组指针,相当于是把一个数的地址给了数组指针,所以指向数组的指针是一个一级指针。*****
***** 指针数组的数组名可以理解成一个二级指针。*****
返回指针的函数:
int *f()表示定义一个名为f的函数,这个函数的返回值是一个int类型的指针。
函数指针:
int(*f)(int)表示这是一个指向函数的指针变量,该函数的返回值类型为int,且具有一个int类型的形参。一般不用写形参,即int (*f)();即可。它要指向一个函数才能有用,指向一个函数之后可以用它来代替该函数。之后使用这个指针相当于使用该函数。
与数组类似,在数组中,数组名即代表着该数组的首地址,类似地,函数名实际上也是执行这个函数任务的代码在内存中的起始地址。因此,函数名就是该函数的函数指针。
因此,我们可以采用如下的初始化方式:
函数指针变量 = 函数名;
同理,我们在用函数指针调用函数时,语法为:
函数指针变量(形参); (就函数指针特殊,其他的均符合我们平常的认知)
其实,函数指针也可以通过“函数指针变量 = &函数名;”的方式来定义,但一般不会这么做,那是历史原因遗留下来的语法。
例如:
int K(int i){}
int(*f)(int)=K; // 等价于f=K。不用f=&K,因为函数名K就是一个地址。
// 但f(1);即可调用函数,不能*f(1); 与int *p2; p2=p1;一致。
函数指针数组:
int (*parr[3])(int,int);
// parr先与 [ ]结合,说明是一个数组,再与*结合,说明数组存放的是指针,指针都指向一个形参为两个int类型,返回值也是int类型的一个函数。
用法:
#include <stdio.h>
int add(int a,int b){
return a+b;
}
int sub(int a,int b){
return a-b;
}
int mul(int a,int b){
return a*b;
}
int div(int a,int b){
return a/b;
}
void make_menu(){
printf("****************************\n");
printf("请选择菜单:\n");
printf("1:加 2:减 3:乘 4:除 0:退出 \n");
}
/*定义函数指针数组变量
(int,int) 对应于函数指针数组指向的函数列表
*/
int (*fun_array[4]) (int,int) = {add, sub, mul, div};
// 函数指针数组内存放的都是同一形参数量及类型、同一返回值类型的函数指针
// 函数名本身就是一个函数指针
int main(){
int i,j;
int cmd;
while(1){
make_menu();
scanf("%d",&cmd);
if(cmd==0){
break;
}
if(cmd>=1&&cmd<=4){
printf("请输入2个数字:");
scanf("%d%d",&i,&j);
//通过函数指针数组去调用对应的函数
int result = fun_array[cmd-1](i,j);
//也可以 int result = (*fun_array[cmd-1])(i,j);
//同函数指针也可以通过“函数指针变量 = &函数名;”的方式来定 义,但一般不会这么做,那是历史原因遗留下来的语法。
//也可以通过函数指针变量来调用对应的函数
//int (*p)(int,int) = fun_array[cmd-1];
//int result = p(i,j);
printf("result:%d\n",result);
}
}
return 0;
}
指向 指针数组 的指针:
定义方式:
char *tBooks[] = {"《数据结构》","《计算机组成原理》"};
char *((*p)[]) = &tBooks;
//(*p)表示p是一个指针,后面配合[]说明他是一个指向数组的指针,把 (*)[] 再 *() 一下表示指向的这个数组是一个指针数组。
//注意,这种命名方式是错误的:char **p[] = &tBooks;
那我们指针的赋值也完成了,该如何通过新定义的指针p取得想要的字符串"《计算机组成原理》"呢?
首先我们得知道,只需要取得 新定义的指针p 指向的指针数组tBooks 的第四个指针元素 即可,因为 %s会帮我们自动打印。
对p解引用,即(*p),取得p存储的地址对应的元素的数据,p是四级指针,刚刚我们让它存储了三级指针tBooks的地址,所以 *p 等价于 tBooks。
有了这个三级指针tBooks,我们就可以对这个三级指针tBooks进行指针运算。当我tBooks+1就代表地址加一个单位,就是tBooks数组第二个指针元素的地址(注意别又在这犯错误了,数组名字加减运算所得到的是元素的地址,而不是元素内所保存的内容,我们选择要取的是元素内容,因为元素内容就是指向字符串的指针。所以我们想要的是tBooks[1], []相当于是对tBooks这个元素的地址进行取值操作,等价于*() 。)
而后我们使 tBooks+1 再解引用,即*(tBooks+1) 写法同 tBooks[1],就可以拿到tBooks的第二个指针元素内的存储的一个一级指针,该一级指针内保存的的字符串常量的首地址。我们拿到这里就够了。我们就需要这个字符串的地址,至于里面的字符,%s会帮我们自动打印。
那么综上所述,这一系列的取地址实际上就是先对四级指针p进行解引用(*p)就得到三级指针所存放的数据,是一个地址(即数组tBooks的首地址)对此地址进行运算+1 即(*p)+1 ,得到的是数组tBooks第二个元素的地址,再次解引用,即 *((*p)+1),得到运算后的地址就是tBooks数组第二个元素存放的数据(因为tBooks是指针数组所以这里的数据还是地址),即字符串的地址,搞定。
printf("请打印:%s",*(*p+1));
指向 函数指针数组 的指针:
#include<stdio.h>
void test(const char *str)
{
printf("%s\n", str);
}
int main()
{
void (*pfun)(const char*) = test;
//函数指针pfun
void (*pfunArr[5])(const char* str);
//函数指针的数组pfunArr
void (*(*ppfunArr)[10])(const char* str) = &pfunArr;
//(*ppfunArr)表示ppfunArr是一个指针,后面配合[]说明他是一个指向数组的指针,把 (*ppfunArr)[10] 再 *() 一下表示指向的这个数组是一个指针数组。(类似于指向指针数组的指针的命名方式)
//指向函数指针数组pfunArr的指针ppfunArr
return 0;
}
补充:指针的右左法:
右左法则(右左法则的英文原文翻译):
首先从最里面的圆括号开始阅读,然后向右看,再往左看。每当遇到圆括号时,就应该调转阅读方向。一旦解析完圆括号里面所有东西,就跳出圆括号。重复整个过程直到整个声明解析完成。其实左右法则就是以标示符为中心点,自右向左旋转解读指针的声明含义。
例如:
int (*a)[10]; 整型数组指针变量
阅读步骤:
1. 从变量名开始 -------------------------------------------- a
2. 往右看,什么也没有,碰到了),因此往左看,碰到一个* ------ 一个指针
3. 跳出括号,碰到了[10] ----------------------------------- 一个10元素的数组
4. 向左看,发现int --------------------------------------- 该数组是int类型数组
int (*a[10])(int); 函数指针数组
阅读步骤:
1. 从变量名开始 -------------------------------------------- a
2. 往右看,碰到一个[10] ------ a是一个含10个元素的数组
3. 往左看,碰到了一个* ----------------------------------- 该数组是一个存放指针的数组
4. 跳出括号,向右看,发现一个(int) --------------------------------------- 指针指向一个形参为
一个int类型的函数。
5. 往左,碰到int --------------------------------------- 函数的返回值是一个int类型
指针可以存放任何变量的地址,但会通过不同的类型去选择不同的读取内存大小。
所以 double a=1,*p=&a;中,p只能存放double类型的变量的地址。(对的)
其中的*是一个说明符,而不是一个间址运算符(取值运算符)。
free(p); //系统将此段空间再次置为可用内存,这样其他的分配就可以使用到
p=NULL; //就是把p指向的箭头给去掉,指向NULL
很显然如果没有free(p);那给p分配的这段大小为1000的内存就永远不能再使用,造成内存泄漏。
如果没有 p=NULL; 那p所指向的地址还是原来的。
A 虽然可以赋值,但这么操作有误,因为不同类型偏移量不同,读取内存不同。
C 可以,且在数组中有运用(比较数组元素的先后顺序)
D 这两个是等价的
加号对指针没有意义,但减号可以判断出两个指针的距离。
\0相当于0,可用于条件语句的判断中。
strlen计算字符串的长度是不包含\0的,但sizeof是包含的,因为\0也是占有尺寸的。
小tips:
如果数组长度确定,那么可以设置一个宏定义,定义数组的长度,然后代码中用宏定义来代替数组长度,这样以后如果想改变数组的长度,只需修改宏定义,不需全文修改。
if(xxx==NULL) 最好写成 if(NULL==xxx),这样如果疏忽把==写成了=,系统会自动报错(不能给常量NULL赋值)。
1、用char数组定义字符串:(注意!定义的是字符数组,不是字符串数组,字符串数组是一个二维数组)
1.char str[6] = {‘h’,‘e’,‘l’,‘l’,‘o’};
如果str的长度刚好等于字符串长度,此时系统将不会自动添加’\0’。
如果数组长度大于字符串长度则会自动添加’\0’。(剩下全部元素都自动补上\0)
2.char str[] = {‘H’,‘e’,‘l’,‘l’,‘o’};
用此方法定义并初始化一个数组系统不会自动添加’\0’.(因为系统认为没有剩下的元素位,所以不会自动补\0),记得手动加上!!!!!!!!!!!!!!!!!!!!!!!!!!!!
3.char str[10] = “Hello”;
4.char str[] = "hello";
系统都会自动在后面添加‘\0’。(因为用双引号系统知道这是一个字符串)
注意 :char str[5]="hello"是错误的写法。
因为在定义字符串时,系统会自动添加’\0’,但是如果数组长度等于字符串长度,因此系统无法给字符串添加’\0’系统将会报错。
注意,因为数组只有在定义时可以一次性给所有的元素初始化,所以如果想用字符数组定义字符串,必须在定义的时候赋值,否则只能使用strcpy()函数对其所以元素进行一次性赋值。
即 char m[40] = "hello,world";
不能 char m[40]; m="hello,world";
因而,如果是在共用体或结构体中定义了字符数组,想在外面给他赋值,必须使用strcpy()函数。
2、用char指针定义字符串:
char *m = "hello,world"; //自动添加'\0'
注意:此时字符串指针m指向字符串常量,不能用*(m+1)='o'修改此常量,因为这个字符串常量放在常量区不能被修改,因而最好用const修饰。
3、数组和指针
数组形式和指针形式有什么不同呢?
数组形式: char m[40] = "hello,world";
指针形式: char *m = "hello,world";
数组形式:
编译器会把数组名m看作是数组首元素的地址&m[0]的同义词,m是个地址常量。可以用m+i来标识数组里的第i个元素,但不能使用++m,增量运算符只能在变量前使用, 而不能在常量前使用。
m[40]在计算机内存中被分配一个有40个元素的数组(其中每个元素对应一个字符,还有一个附加的元素对应结束的空字符'\0')。每个元素都被初始化为相应的字符。
通常,被引用的字符串存储在可执行文件的数据段部分;当程序被加载到内存中时,字符串也被加载到内存中,把被引用的字符串复制到数组中
指针形式:
指针形式(*m)也会在静态存储区为字符串预留空间。此外,一旦程序开始执行,还要为指针变量m另外预留一个存储位置,用于在该指针变量中能够存储字符串的地址。
m指向字符串的第一个字符,可用m+i指向第i个字符,或使用++m指向第二个字符。 指针m是个变量。
A错,数组定义后不能一次性给他赋值!(char[8]={“Beijing”};是可以的)
B错,指针不能与 {} 搭配使用。
C错,数组只要在定义时才能一次性给多个元素赋值。
D对,只是定义了一个字符串,然后让他指向常量区的“Beijing”字符串
*p+4和*(p+4)不同,前者是先取值在加4,后者是先地址后移4个单位然后再取值。
getc和getchar都是输入一个字符。
函数原型:int getc(FILE *stream); int fgetc(FILE *stream);
两者都是从指定的流中读取字符,可从文件中,也可从缓冲区中。
如:getc(stdin); .......
因而fgetc和getc在一定程度上是可以等价的。
两个都是用来从stream中取得一个字符,并且把文件位置指针忘下一个字符移一位,区别在于调用getc函数时所用的参数stream不能是有副作用的表达式(稍后解释),而fgetc函数则可以,也就是说,getc可以被当作宏来调用,而fgetc只能作为函数来调用。
fgetc是一个函数 (不会有宏的副作用)
getc是一个宏(速度更快,不需要堆栈)
scanf以第一个非空白字符开始读入,读到(但不包括)下一个空白字符(可以是空格、制表符或者换行符),%s不能录入空格,gets可以录入空格。
而且sacnf在%c的状态下输入时还可能要搭配fflush使用。
(最早学的用得最多的就是最多毛病的)
在字符(ASCLL)中,0转为十进制为48,\0转为十进制为0。
char a[][5]={"A","B","CCC"}; // 定义字符串数组
printf("%s",a[2]);
char *b[5]={"A","B","CCC"}; // 定义一个指针数组,里面存放了指向这几个字符串的指针
printf("%s",b[2]);
char c[3][5]={"A","B","CCC"}; // 定义字符串数组
printf("%s",c[2]);
字符串数组的每一个第一维(左中括号)一个字符数组,输出方式与输出字符数组一致,直接printf("%s",第一维);。如:字符串数组是printf("%s",c[n]);。而字符串的指针数组(例二)因为存放的是指向字符串的指针,所以输出时是printf("%s",b[n]);。其实都是在printf中放一个直接指向字符串的指针即可输出字符串。
要注意的是,字符串数组不等价于字符数组,字符数组是一个存放字符的数组,是一个字符串,字符串数组是一个二维数组,存放了n条字符串,是一个存放字符串的一组数组。
结构体类型可以定义函数,
如:struct A f(struct A t){ };
定义一个结构体类型的函数,然后形参是一个结构体类型的变量。
*pk[3]表示创建一个指针数组,然后里面存放的是一个指向int类型(基类型为int)的指针,不可以是指向指针的指针,即不能是二级指针,如int k[2][3]中的k。
字符串的结尾可以赋值为 ’\0’ ,或者直接赋值为0也许,这两个是等价的。
复制字符串的时候,别忘了把字符串的\0也复制过来。
没include <string.h>但使用strlen()编译器是不会报错的,但会出bug。
统计a到z的数量:
第二种更好,因为第一种如果输入的不是a到z,就很有可能修改到了数组以外的数据,要出大问题!别省事,加个判断不会死。
答案是C,只会交换第一个元素。
int b;
int a=b=1; // 可以同时给a b赋值,前提是b已经定义了。
// 原理:赋值运算符的右结合,让b赋值为1,再把b赋值给a。
BD中把p指向末节点后,把p的next赋值给s的next,其实赋的不是地址,而是NULL,所以没错。而A把s的next指向了p,就不是单向链表了。
struct workers{
int num;
}x,*p;
int main()
{
x.num=111;
p=&x;
printf("%d",p->num++); // 取值输出然后值加加
printf("%d",(*p)->num); // 语法错误
printf("%d",++p->num); // 取值且值加加后输出
printf("%d",(++p)->num); // 语法错误,虽然编译通过,但并不能正常输出。
// num占4个字节,p++会使p+4,但不知道下一个地址存放了什么,无意义。
不是“结构体类型的数组的指针”的结构体指针不允许自增自减操作。类似于普通的指针如果不指向数组,那么自增自减也毫无意义,因为你不知道你的下一个地址里面存放了什么。
return 0;
}
typedef struct workers{
char name[10];
}stu;
void f(char* p){
strcpy(p,"aaa");
}
int main()
{ stu x={"asdc"},y;
y=x;
f(y.name);
printf("%s",y.name); // 输出aaa,f函数可以对y的name进行修改。
// 因为name在结构体中就是一个数组,数组名就是地址。
return 0;
}
上述代码中,std或p没有加[]符则默认表示第一个元素,别忘了。
另外,“.”和“->”以及“()”运算符都是属于第一优先级,而&属于第二优先级,
所以scanf("%c",&p->sex);
scanf("%c",&(*p).sex); 都是可以正常输入的,不用特意加括号再取址。
答案为B。数组不同于其他变量需要取址,数组名字本来就是地址,直接scanf(“%s”,std.name);即可。
枚举类型的定义中因为代码是从左到右执行的,所以可进行枚举类型的加减操作(执行到的时候已经定义了)。
链表运用while进行循环更好,因为可以直接whlie(p!=0)判断是不是末节点NULL。
注意,不允许whlie(*p),*p是结构体变量,应该是whlie(p)。 // p = NULL;等价于p=0;
结构化程序设计原则:
1.自顶向下:程序设计时,应先考虑总体,后考虑细节;先考虑全局目标,后考虑局部目标。不要一开始就过多追求众多的细节,先从最上层总目标开始设计,逐步使问题具体化。
2.逐步求精:对复杂问题,应设计一些子目标作为过渡,逐步细化。
3.模块化:一个复杂问题,肯定是由若干稍简单的问题构成。模块化是把程序要解决的总目标分解为子目标,再进一步分解为具体的小目标,把每一个小目标称为一个模块。
4.限制使用goto语句
结构化程序含有三种基本结构:顺序,选择(分支),循环(重复)。可以说由这三种结构组成。
答案错误,正确答案为B!
C语言是一种结构化程序设计语言。
C语言编写的程序经过编译解释才能运行。错!!!!
还要连接!!!!不然库函数就没办法执行。
quitChar = getchar();
while(getchar() != '\n');
这两行代码可让quitchar在接收了一个字符之后,清除缓冲区。
while ((ch = getchar()) != ' \n ' )
fputc(ch, fp);
这两行代码可接收键盘输入的除回车以外的字符添加到文件中。
文件指针stdin其实就是缓冲区,在计算机内就已经定义,可在C源程序中直接使用。
如fflush(stdin);
因为stdin是一个文件指针,所以可以直接使用f开头的文件操作函数。
如fgetc(stdin);就是从缓冲区读取字符,等价于getchar();
程序与数据的交互是以流的形式进行,文件由输入流形式组成。 答案为C。
注意A。 分清文件位置指针(IO流上的文件指针)和文件指针的区别,前者是光标位置,表示当前读写数据的位置,后者是指向文件的“FILE类型变量”。
文件指针并不指向文件的储存位置。文件指针实际上是一个指向由系统定义的结构体的指针,结构体是在文件打开时由系统自动创建的,里面包含了各种处理文件所需要的信息。文件指针的移动是指在文件之间来移动。
注意:这个文件内部的位置指针与C语言中的指针不是一回事。位置指针仅仅是一个标志,表示文件读写到的位置,也就是读写到第几个字节,它不表示地址。文件每读写一次,位置指针就会移动一次,它不需要你在程序中定义和赋值,而是由系统自动设置,对用户是隐藏的。 而文件指针则是C语言中的指针。
A错,只有文本文件是以EOF作为结束标志。
因为在文本文件中,数据是以字符的ASCII代码值的形式存放,ASCII代码的范围是0到255,不可能出现-1,因此可以用EOF作为文件结束标志。
当把数据以二进制形式存放到文件中时,就会有-1值的出现,因此不能采用EOF作为二进制文件的结束标志。为解决这一个问题,C语言提供一个feof函数,用来判断文件是否结束。feof函数既可用以判断二进制文件又可用以判断文本文件。
B错,只要读入的还是字符,则返回所读入字符,最后一个字符读完了才返回EOF。
C错,EOF值为-1.
D对,记住,是C定义的,在库函数中定义的,而不是windows。
- (被)包含文件中可以包含其他文件
- 修改了得从新连接才能生效,就像修改了源程序不重新编译一样按以前的来那样。
- 对
- #include可以出现在任意位置
A.B.C.分清文件位置指针(IO流上的文件指针)和文件指针的区别,前者是光标位置,表示当前读写数据的位置,后者是指向文件的“FILE类型变量”。
文件指针并不指向文件的储存位置。
答案为D。
因为在调用fopen()函数的时候其文件路径是一个字符串(用双引号括起来了),所以文件路径后面其实隐藏了 \0 ,系统会默认不读取,正因如此,我们可以把文件路径放进字符数组中或定义字符串指针,然后在调用函数时在实参打上数组名或指针名即可。
如
char sfname[20] ="myfile1.txt";
myf=fopen(sfname,"w");
char *p ="myfile1.txt";
myf=fopen(p,"w");
正因为有\0的存在,才使得该程序可以将字符串ABC完完整整得复制到myfile2中。
忽略while循环内没用的putchar和printf语句,然后只剩下:
ch=fgetc(fs);
whlei(!feof(fs))
{
fputc(ch,ft);
ch=fgetc(fs);
} // 先取一个字符,然后判断是不是到了文末,不是就打印到目标文档上,然后又取一个,再判断,再打印,然后又取,又判断,又打印。直到取出\0,注意,\0也并不是文末,ch为\0,但fs已经指向了文末(\0之后(每取一次自动向后移一位)),就不循环了,但字符串除了\0其他都复制到目标文档里了。
在函数a中直接调用函数a本身就是直接递归调用。
在函数a中调用另外一个函数b,而该函数b又调用了函数a就是间接递归调用。
创建链表节点可以使用malloc函数来创建一个结构体类型的内存块,也可以创建一个结构体变量,然后用再定义一个结构体类型的指针来只指向这个变量,但太过于麻烦,所以一般都是使用malloc函数来创建链表节点。
malloc的注意事项:
(1)malloc函数他只分配内存,但不会对内存进行初始化,其空间中的值是随机的。
(2)使用malloc一定要注意分配内存的大小,比如说:
int *p;
p=(int*)malloc(1);
如果这样的,代码是不会报错的,但是这个时候你只分配了1个字节给p,但是p是int类型,具有4个字节,剩下的3个字节就会向后面分配,这样就占用了别人的空间,结果就导致别人原本的值就消失了。
(3)malloc是手动申请内存,我们在结束程序的时候也要手动将这些内存释放出去。这个时候我们就需要用到free函数了。
链表示例:
//链表创建、遍历、头插、尾插、法1法2删除相同元素、删除、判断链表是否为空
#include<stdio.h>
#include<stdlib.h>
typedef struct Node{
int data;
struct Node *next;
}Node;
void createLink(Node *head,int size); // 创建链表
void travelLink(Node *head);
void insertForward(Node *head,int data); //头插法
void insertBack(Node *head,int data); //尾插法
void DeleoneList(Node *L);
void DeletwoList(Node* L);
void deleteLink(Node *head);
void isEmpty(Node *head);
int main(){
int i,size,num;
//主函数中不能指定一个头指针,应该定义一个头指针指向头结点
Node *head = (Node*)malloc(sizeof(Node));
head -> next = NULL;
void (*cha[10])(Node*,int)={insertForward,insertBack};
void (*array[10]) (Node*) = {DeleoneList,DeletwoList,deleteLink,isEmpty};
printf("命令:\n0-创建链表,1-遍历链表,2-头插法,3-尾插法,4-法1删除相同元素,5-法2删除元素,6-删除链表,7判断链表是否为空,8-退出\n\n");
while(1){
printf("请输入命令:");
scanf("%d",&i);
switch(i){
case 0:
printf("请输入要创建的链表长度:");
scanf("%d",&size);
createLink(head,size);
break;
case 1:
travelLink(head);
break;
case 2:case 3:
printf("请输入要插入的元素:");
scanf("%d",&num);
cha[i-2](head,num);
break;
case 4:case 5:case 6:case 7:
array[i-4](head);
break;
case 8:
return;
}
}
return 0;
}
//创建链表
void createLink(Node *head,int size){
Node *rear = head;
int i;
printf("请输入存入链表的数据:\n");
for(i = 0;i < size;++i){
Node *newnode = (Node*)malloc(sizeof(Node));
newnode -> next = NULL;
scanf("%d",&newnode->data);
rear -> next = newnode;
rear = newnode;
}
}
//遍历链表
void travelLink(Node *head){
Node *p = head -> next;
while(p != NULL){
printf("%d\t",p->data);
p = p -> next;
}
putchar('\n');
}
//头插法
void insertForward(Node *head,int data){
Node *newnode = (Node*)malloc(sizeof(Node));
newnode -> next = NULL;
newnode -> data = data;
newnode -> next = head -> next; // 此时newnode和head的next都指向头节点的下一个元素
head -> next = newnode; // 把头节点指向newnode(头节点依旧是头节点)
} // 注意最后两个语句的顺序不能变,不然就失去了原第二个节点的指向,找不回来了
//尾插法
void insertBack(Node *head,int data){
Node *newnode = (Node*)malloc(sizeof(Node));
newnode -> next = NULL;
newnode -> data = data;
Node *p = head;
while(p->next != NULL){
p = p -> next;
}
p -> next = newnode;
p = newnode;
}
void DeleoneList(Node *L)
//删除方法一 :遍历原链表,建立新链表,如果有相同,则不加入,如果没有,则加入
{
printf("第一种删除方法:\n");
Node *head,*p,*q,*r,*m; //建立新链表
head = (Node*)malloc(sizeof(Node)); //新链表头节点
head ->next = NULL;
q = head; //q和head都指向新链表的头节点
p = L->next; // p指向旧链表的第二个节点(第一个元素)
int flag = 0;
while(p!=NULL)
{
for(m = head->next;m!=NULL;m=m->next)
{ //m为新链表的第一个元素
if(p->data==m->data)
//这个for循环的过程中,p是不变的,用m的移动遍历新链表,所以起到了拿p的数据与新链表的全部数据做对比的效果
{ // 刚开始新链表里什么元素都没有,当后面插入新数据后,就有可能与旧链表的元素相同
flag = 1;
break;
}
}
if(flag==0) //可以插入新数据
{
r = (Node*)malloc(sizeof(Node)); //r是新链表的新节点
r->data = p->data;
r->next = q->next;
q->next = r;
q=r; // q刚开始是指向新链表的头节点
}
flag = 0;
p = p->next; //下一个数据
}
L=head;
printf("结果如下:");
travelLink(L); //输出结果
}
void DeletwoList(Node* L)
//删除方法二:进行双重循环遍历,外循环当前遍历的结点为cur,内循环从cur开始遍历,相同则删除
{
printf("第二种删除方法:\n");
Node *p,*q,*r,*m;
p = L->next;
while(p!=NULL)
{
q = p;
while(q->next!=NULL)
{
if(p->data==q->next->data) //这个过程中,p指向的节点不变,直到while循环结束。
{
q->next = q->next->next; //删除q->next这个节点
// 小缺点,没有free上一个节点,可以再定义一个指针跟随q来解决,我就懒得写了嘿嘿。
}
else
{
q = q->next;
}
}
p = p->next;
}
printf("结果如下:");
travelLink(L); //输出结果
}
//删除链表
void deleteLink(Node *head){
Node *curr;
while(head -> next != NULL){
curr = head -> next;
head -> next = curr -> next;
free(curr);
}
}
//判断链表是否为空
void isEmpty(Node *head){
if(head -> next == NULL){
printf("链表为空!\n");
}else{
printf("链表不为空!\n");
}
}
注意;
Node* head,p,q,r,m;
该行代码中,只有head被定义成了结构体指针类型,其余的均为结构体类型。
别忘了,数组是不能用变量来定义长度的,虽然可能编译通过,但运行时会出bug。
数组元素可以是表达式,但只占一个元素位。
如int a[5]={1*5};
若实参是一个二维数组,那么在定义接收该数组的形参时,要指定形参数组第二维的长度从而让计算机能把数组拆分成n行。(与二维数组的定义同理)
字符数组的长度和字符串的长度是两个不同的概念:
如: char c[10]={“hello”};
上述代码中,字符数组的长度是10,字符串的长度是5(字符串的长度是不包括\0的,%s打印的时候也不会打印出来)
指针变量的类型不是指针的类型,是指针指向的变量的类型,它决定了用指针读取时读取的内存大小。
指针所指向的数据的数据类型被称为基类型。一个指针变量只能指向同一基类型的数据对象。
指针变量本身占四个字节。
指针的基类型是他运算的基本单位。比如int类型的指针如果加一,那么会加4个字节从而指向下一个元素。
对于二维数组a[5][5]来说,a+i是一个行指针,指向某一行,一行就是一个一维数组。
对行指针取值就是列指针,所以*(a+i)就是一个列指针,默认指向下标为i的那一行的第一个元素(指向而已,并不是元素本身),*(a+i)+j就是指向下标为i的那一行的下标为j的元素。
对列指针取值就是元素本身,所以*(*(a+i)+j)就是a[i][j]。
[i]就包括了取值和加偏移量i的操作,所以a[i]就是一个列指针,指向下标为i的那一行,默认指向第一个元素。a[i]+j即指向下标为i的那一行的下标为j的元素。
*(a[i]+j)就是元素本身,等价于a[i][j]。
(*(a+i))[j]也是元素本身,等价于a[i][j]。
行指针是一个二维指针,列指针是一个一维指针。
行指针取值变列指针,列指针取值变元素本身。元素本身取址变列指针,列指针本身取址变行指针。
稍微变态一点的(二维数组a[M][N]):
元素的地址: a[0]+i*N+j / &a[0][0]+i*N+j
(a[0]就是保存了a[0][0]的地址,即指向a[0],等价于&a[0][0])
元素本身: *(a[0]+i*N+j) / *(&a[0][0]+i*N+j)
注意:元素的地址那里,不能a[0][0]+i*N+j,因为a[0][0]是元素本身了,这只是元素本身的算术运算。
若有一维指针a[5],且int (*pa)[5]=a; 则*pa+i是指针的指向移动i位。
若有二维指针a[5][5],且int (*pa)[][5]=a; 则*pa+i是指针的指向移动i行。(行指针)
int *(pd[3]) 等价于 int *pd[3],即定义了一个指针数组。
在调用函数传实参时,如果传的是一个二维数组,因为形参在定义二维数组来接受时必须定义第二维,所以这样就固定了所传实参必须与所定义的形参的第二维一致,影响了函数的通用性。
我们可以定义一个一维数组去接收二维数组的首元素的地址,一维二维数组的内存也是连续的,我们就可以通过公式i*n+j来让形参的一维数组访问实参的二维数组元素a[i][j],n位原函数二维数组的第二维的长度。
如:
int main(void){
int a[3][5]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
output(&a[0][0],3,5);
}
void output(int arr[],int m,int n){
int i,j;
for(i=0;i<m;i++){
for(j=0;j<n;j++){
printf(“%d”,arr[i*n+j]);
}
}
}
int p; *p=“abc”;
错误,指针定义字符串不用*取值,因为这个过程是将abc字符串的首地址赋值给p。
别忘了calloc函数:
void* calloc(unsigned int n , unsigned int size);
作用:在内存的动态储存区分配n块、每块大小为size字节的连续的内存空间。
与malloc函数不同的是:
(1)、calloc可以一次分配多个空间。
(2)、系统对calloc函数所分配的内存空间都会自动赋上\0。(性质与数组相同)
这样我们就可以利用calloc函数来动态创建一维数组了。
free函数只能释放malloc或calloc函数所分配的内存空间。
调用malloc或calloc函数来分配内存空间并不是每一次都会成功,不成功则返回NULL指针,所以在调用这两个函数时应该加个判断。
malloc和calloc所分配的内存空间不会主动释放,要记得手动free掉。
指针常量和常量指针的定义:
- 常量指针:(指向常量的指针)(定义时const在*的左侧)
如: const char *p = “abc”;
常量指针指向的对象(即常量不能被修改),但指针可重新赋值指向其他变量。
- 指针常量:(是一个常量,只不过这个常量是一个指针(地址))(定义时const在*右侧)
如: const * const p = &a;
常量内容不能变,但其指向的变量的内容可变。且在定义的同时必须初始化,因为定义完后就不能改变它的值了。
(数组名也是指针常量)
strcpy(s1,s2);
1、是将s2的字符串复值到s1中来。
2、不需要接收返回值。
3、会把s2的\0也复制进去。
4、会把s1全部的字符覆盖掉,包括长于s2的那部分。
注意啊!!s1不能是指向常量的指针,常量是不允许修改的啊!
strcmp(s1,s2)
- 自左向右逐个按照ASCLL码值进行比较,直到出现不同的字符或遇’\0’为止。
- 不是比较所有字符的ASCLL码之和。
strcat(s1,s2)
- 把s2的字符串接到s1后
strlen(s1,s2)
- 返回长度不包括 \0
当字符串存储在字符数组内时是不能用sizeof来判断字符串长度的!!!
strchr(s1,s2)
- 找出字符串s1中字符s2首次出现的位置
2、返回地址值
strstr(s1,s2)
- 找出字符串s1中字符串s2首次出现的位置
- 返回地址值
注意:gets可以接收空格或Tab,puts最后都是以换行符来结束!(不用手动加\n)
别忘了结构体、共用体、枚举类型的定义的大括号后要加分号;
如果结构体内嵌套了一个其他的结构体,且存在一个外层结构体指针,那么这个指针只能访问到外层结构体内的变量,无法直接用->访问到内层结构体的变量。(内层的结构体也不能用->直接访问)
如:
typedef struct Student
{
char name[10];
}Stu;
typedef struct Qlist
{
Stu p_1;
}q;
int main()
{
q a,*pa;
pa->name; //错误!
return 0;
}
共用体中存放了int类型的i和double类型的d,虽然i的值为5,但输出d不会5.000000,因为不同的类型的二进制解释方法不同。
各枚举常量的值可以重复。例如:
enum fruit_set {apple, orange, banana=1, peach, grape}
//枚举常量apple=0,orange=1, banana=1,peach=2,grape=3。
枚举常量只能以标识符形式表示,而不能是整型、字符型等文字常量。例如,以下定义非法:
enum letter_set {'a','d','F','s','T'}; //枚举常量不能是字符常量
enum year_set{2000,2001,2002,2003,2004,2005}; //枚举常量不能是整型常量
枚举类型变量只能接收枚举元素,不能接收整形数值,虽然他们本质是相同的。
( 但可强转: today=(weekday)1 )
枚举类型不能进行算术运算,如加减乘除、自增自减等。
枚举类型只能参与赋值和关系运算以及输出操作,且做关系运算时不限定只能枚举类型间关系运算。
如:today==Fir / today==5
枚举变量占用内存的大小与整型数相同。
枚举变量可以直接输出,输出的是变量的整数值。
进制转换记忆:
总结1:
十 转 二 , 除2取余法
十 转 八进制 , 除8取余法 除到商为0
十 转 十六 , 除16取余法
总结2-1:(该法也称为“按权展开”)(按权展开底数都是原进制)
二 转 十 , 各位数*2^n
八 转 十 , 各位数*8^n n从0开始。最后求和。
十六 转 十 , 各位数*16^n
总结2-2:(每三位单独“按权展开”,次方从0到2往复)
二 转 八 , 也是“按权展开”,但是是从低位开始,每3个转为一个,然后拼接起来。
二 转 十六 , 也是“按权展开”,也是从低位开始,每4个转为一个,然后拼接起来。
(二转十是全按权展开然后求和,二转八是三位展开然后拼接,二转十六是四位展开然后拼接) (二进制小,好欺负,转其他都要次方)
总结3:
八 转 二,把每一位看成一个十进制,然后转为对应的三位的二进制,不够用0补,最后拼接。
十六 转 二,把每一位看成一个十进制,然后转为对应的四位的二进制,不够用0补,最后拼接。
至于八转十六、十六转八:
建议先转成十进制然后再相互转换。
C语言的long类型占4个字节,long double类型占12个字节。
用const修饰变量时,都要在初始化时赋值,因为以后就改不了了。
goto语句的使用方法别忘了:
标号:语句; goto 标号;
了解一下:
extern 用来声明已经定义的外部变量。
函数外不能有赋值等运行时的操作语句。
1e-7就是1乘10的-7次方的意思了,但1不能省。
char *p;
printf("请输入数字字符串:");
gets(p); // 这样子是错误的,因为p指针并没有指向可写入的内存空间
正确写法应该要用malloc或calloc来为指针p分配内存空间。
冒泡排序外层用自减运算符更方便易懂:
for(i=3;i>=1;i--){
for(j=0;j<i;j++){
if(ch[j]>ch[j+1]){
c=ch[j];
ch[j]=ch[j+1];
ch[j+1]=c;
}
}
}
结构体类型所占字节并非是各个内部类型的占用字节之和。
不能用pp[i]!=’\0’;这种方式来寻找int类型的数组的末元素,只有char类型的数组才能这样做。
读取一段空格分隔的数字:
int a[100];
int i=0;
scanf("%d",&a[i]);
while(getchar()!='\n'){
scanf("%d",&a[++i]); // getchar()将空格取走后还有空格也不会被scanf()所读入,因为%d读入的时候会忽略第一个数字前的空格、Tab键等。所以就算空格有一百个代码也不会出问题。
}