c语言学习笔记结合教程《c和指针》.docx
- 文档编号:10539809
- 上传时间:2023-02-21
- 格式:DOCX
- 页数:16
- 大小:24.87KB
c语言学习笔记结合教程《c和指针》.docx
《c语言学习笔记结合教程《c和指针》.docx》由会员分享,可在线阅读,更多相关《c语言学习笔记结合教程《c和指针》.docx(16页珍藏版)》请在冰豆网上搜索。
c语言学习笔记结合教程《c和指针》
1.基本类型的赋值,转换问题。
[
(1)见p80;]
(1)c中是不是也和Java一样,存在隐式转换和强制转换?
有区别吗?
有区别。
C中的隐式转换就是“整型提升”。
C中的“整型提升”仅指:
表达式中的操作数类型<=int的情况下(short和char),提升到int型。
(注意:
是表达式中的char和short在使用之前被转换为int。
)
>=int型的类型的提升,被称为寻常算术转换:
寻常转换应该遵循:
int–unsignedint–longint–unsignedlongint–float–double–longdouble由小到大的转换规则:
一个操作数类型相对另一个操作数类型排名较低(较小),则自动转换为相对较大的类型。
(数据转换按数据存储长度增长的方向进行。
)
寻常算术转换和强制转换均为“算术转换”。
寻常算术转换侧重于自动转换到相对较高的类型(如int-float),强制转换则可人为将其转换到其他类型(如long–int;int-double)。
(记住:
是先转换,再执行操作。
)
(记住:
若某个操作符的各个操作数属于不同的类型,则除非其中一个操作数转换为另一个操作数类型,否则操作无法进行。
问:
该规则是否也针对赋值操作符“=”?
不过等号右边的操作数(右值)自动转换成了左值的类型。
)
(注意:
以上概念是针对算术操作符而言的。
算术操作符就是+-*/%。
问:
那对于其它操作符呢?
尤其是关系操作符?
)
(2)不同类型操作数互相赋值时的精度变化情况:
从int到float:
精度可能会降低(有点例外)。
Float只能保证6位有效数字的精度(浮点型以指数方式存储),虽然长度为4个字节。
在32位平台上,通常int是4字节长度,最多表示到21亿多,而int型是“绝对精确”的,换句话说,就是int行最多可以保证10位十进制有效数字的精确度。
而float只能保证6位有效数字的精确度,因此int到float的转换是可能丢失精度的。
比如整数“”转换成float后,大约是:
1.乘10的9次方,也就是从第7位有效数字开始已经不准确了。
double可以保证15位10进制有效数字的精度,所以从int到double不会有这个警告。
(以上摘录自网络。
)
(3)左值与右值的注意点?
左值可以是变量或表达式,但必须能标识一个可以存储结果值的地点。
2.位运算问题:
(1)无符号数与有符号数左移,右移(<<,>>)的异同?
它们的左移均为逻辑移位(补0);无符号数的右移也是逻辑移位(因为无符号数无须担心符号位问题);但有符号数的右移方式(逻辑移位还是算术移位)取决于编译器的类型(因而是不可移植的)。
(2)逻辑移位与算术移位的区别?
逻辑移位补0;算术移位(似乎只用于右移):
左边移入的位根据有符号数符号位值决定是0还是1。
3.各种数据类型的长度及注意点
(1)整型:
(长整型至少应该和整型一样长,整型至少应该和短整型一样长。
)
char,signedchar,unsignedchar,shortint,unsignedshortint,int,unsignedint,longint,unsignedlongint
其中,shortint至少16位,
limits.h中说明了各种不同整型类型的特点:
p30
signed
unsigned
类型
最小值
最大值
最大值
字符
SCHAR_MIN(-128)
SCHAR_MAX(127)
UCHAR_MAX(0xff)
短整型
SHRT_MIN(-32768)
SHRT_MAX(32767)
USHRT_MAX(0xffff)
整型
INT_MIN
(--1)
INT_MAX
()
UINT_MAX
(0xffffffff)
长整型
LONG_MIN
(-L-1)
LONG_MAX
(L)
ULONG_MAX
(0xffffffffUL)
整型字面值(整型字面值常量):
可以是9种整型中的任何一种。
(2)浮点类型:
longdouble至少和double一样长,而double至少和float一样长。
头文件float.h中有记录:
MAX
MIN
float
FLT_MAX(3.e+38F)
FLT_MIN(1.e-38F)
double
DBL_MAX
(1.23158e+308)
DBL_MIN
(2.72014e-308)
longdouble
LDBL_MAX
(DBL_MAX)或
(1.e+4932L)
LDBL_MIN
(DBL_MIN)或
(3.e-4932L)
注:
浮点数字面值总是写成10进制的形式,必须有一个小数点或一个指数。
(3)指针
4.static,extern,const
5.C中的抽象数据类型(ADT:
abstractdatatype)是什么?
也称“黑盒”。
由接口和实现2部分组成。
接口是公有的,一般定义在头文件中,说明用户如何使用ADT提供的功能;实现是私有的,是实际执行任务的部分(实现细节对客户不可见)。
比如:
可将常量或函数等的申明放在头文件中,函数的定义放在源文件中。
而这些函数还可以调用更为细节的且被定义为static的处理函数(static函数对外不可见)。
具体可见例子p125。
6.关于提高程序效率的一些建议:
(1)尽量使用编译时求值的表达式(如常量表达式),而减少使用运行时求值的表达式(代价更高)。
(2)在for循环里减少计数器的使用(可能的话),如下:
(p148)
#defineSIZE50
intx[SIZE];
inty[SIZE];
voidtry()
{
registerint*p1,*p2;
for(p1=x,p2=y;p1<&x[SIZE];)
*p1++=*p2++;
}
(3)数组操作时:
使用指针往往比使用下标更有效率(但不一定);指针的效率永远不会低于下标(理论上)。
(4)声明为寄存器变量的指针通常比位于静态内存和堆栈中的指针效率更高。
(5)自动变量(尤其是数组)如果在函数或代码块中经常要被初始化,可以考虑将其设为static,这样只需在程序开始前初始化一次。
(6)结构中成员之间的边界对齐问题:
p206
结构的起始位置必须是结构中边界要求最严格的数据类型所要求的位置,如下:
structALIGN{chara;intb;charc;};
sizeof(ALIGN)则显示要占12字节。
因为int型的存储位置必须能被4整除,结构起始位置则也一样。
所以3个成员将各占4个字节。
如果改为这样:
structALIGN{intb;chara;charc;};
则只占8字节(其中2个字符紧挨在一起)。
(必要时需对结构中成员的排序进行优化。
)
单个字节(char)能对齐到任意地址
2字节(short)以2字节边界对齐
4字节(int,long)以4字节边界对齐
sizeof得到结构的整体长度,包括因边界对齐而跳过的字节。
宏offset(定义在stddef.h中)确定结构中某个成员的实际位置。
如:
offsetof(structALIGN,b)
7.运算符优先级中一些注意点,一些比较重要的优先级:
(1)*与++/--的结合
从高到低:
.->++(后缀)--(后缀)!
++(前缀)--(前缀)*(间接访问)
&(取址)
8.指针,数组中的注意点(多重指针,指针数组,多维数组)
(1)数组名是一个指向某某型的常量指针(是常量!
),它的值是第一元素的地址。
但有两个例外:
被用作sizeof或&的操作数。
前者返回整个数组的长度;后者产生一个指向数组的指针(不是指向某某型的常量指针)。
P141。
(2)以下3者等价:
array[2];2[array];*(array+2);
(3)inta[5];和int*b;的区别:
p150
声明数组,则同时根据指定的元素数量分配了内存空间,并以作为常量指针的数组名指向这段内存空间的起始位置。
声明指针变量,编译器只为指针本身保留内存空间,不为任何整型值分配内存空间。
所以:
此时*a是合法的,*b是非法的。
但b++能编译,a++却不能(常量)。
做测试:
char*a=“abc”;charb[]=‘”abc”;char*c=a;各自的++。
(4)数组声明时的初始化:
inta[5]={2,5,1};则后2位自动设0。
Inta[]={1,2,3,4,5};则长度自动设为5。
(5)chara[]=‘”Hello”;
char*b=“Hello”;
两者效果一样,但含义不同。
P153。
(6)2维数组一定要理解为数组中包含子数组的形式。
理解:
p157(想像一下指针数组,但并不一样)
inta[3][10];其中:
a(指向子数组的指针,好比指向指针的指针)
a+1(指向第2个子数组的指针)
*(a+1)(指向子数组中第1个整型值的指针(仍是指针!
))
*(a+1)+5(指向子数组第6个整型值的指针)
*(*(a+1)+5)(子数组第6个整型量的值)
以下等价:
*(a+1)和a[1]
*(a+1)+5和a[1][5]
(7)c中若写a[3,4],则等同于a[3]。
p158
9.C中函数的参数都是按值传递的(拷贝形式,无须担心值被改,但数组会被改)。
包括指针!
将实参的指针的拷贝赋给形参,形参可直接修改所指内存地址中的值,但指针是以拷贝的形式传递的,所以还是按值传递的。
好处是并未破坏原来的指针。
所以:
值传递易产生“切割问题”。
如下:
ClassWindow{
Public:
Virtualvoiddisplay()const;
};
ClassWindowWithScrollBars:
publicWindow{
Public:
Virtualvoiddisplay()const;
};
VoidprintNameAndDisplay(Windoww){
w.display();
}
WindowWithScrollBarswwsb;
printNameAndDisplay(wwsb);
因为参数仅仅是拷贝,所以不可能把WindowWithScrollBars中不属于Window的部分复制给Window对象。
所以调用的是WindowWithScrollBars的display(),哪怕它是虚函数。
所以要采用byreference-to-const的方式,当然,这仅指对象,若是内置类型入int,还是用passbyvalue较好(见《Effectivec++》p88)
10.C中常用的字符,字符串处理函数:
(注:
size_t定义在stddef.h中,代表无符号整数。
)
关于查找:
(1)size_tstrspn(charconst*str,charconst*group);p182
返回str起始部分匹配group中任意字符的字符数(也可理解为str中第一个不在group中出现的字符的索引值)。
如下:
intlen1=strspn(“”,“af123af”);//len1=3
(2)size_tstrcspn(charconst*str,charconst*group);
返回str起始部分不匹配group中任意字符的字符数(str中第一个在group中出现的字符的索引值)。
如下:
intn=strcspn(“Hello,world”,“Welcomeyou”);//n=1
(注:
若找不到,则返回str的尾部。
)
(3)char*strpbrk(charconst*str,charconst*group);
返回str中第一个与group中任何一个字符相匹配的字符的地址(指针)。
(注:
若没找到,返回NULL。
)
(注:
它与strcspn的区别是,前者返回指针,后者返回下标值。
)
(4)char*strctr(charconst*str,intch);
(5)char*strrchr(charconst*str,intch);
前者返回在str中第一次出现的字符ch(int型)的位置,后者返回在str中最后一次出现的ch的位置。
(6)char*strstr(charconst*s1,charconst*s2);
在s1中查找整个子字符串s2第一次出现的起始位置。
(注:
若找不到,返回NULL,若s2是空字符串,返回s1。
)
关于长度:
(7)size_tstrlen(charconst*string);
返回string包含的字符的个数。
(注:
不包括\0的长度。
写inta=strlen(“ABC”);或inta=strlen(“ABC\0”);
结果都为3。
)
关于复制与连接:
(8)char*strcpy(char*dst,charconst*str);
将src字符串复制到dst中(连带NUL字节)。
返回指向dst数组的指针。
原来dst中的内容被覆盖。
(注:
若src和dst在内存中是重叠的,则结果是未定义的。
)
(注:
不要让str的长度大于dst的长度。
)
(9)char*strcat(char*dst,charconst*src);
将src添加到dst的末尾,返回指向dst数组的指针。
(注:
若重叠,未定义。
)
(注:
要保证dst剩余空间足够。
)
关于比较:
(10)intstrcmp(charconst*s1,charconst*s2);
字典比较。
比较2个字符串中最先不匹配的字符的大小(依据字符集)。
若s1
若s1=s2,返回0。
长度受限的字符串函数:
(11)char*strncpy(char*dst,charconst*str,size_tlen);
(12)char*strncat(char*dst,charconst*str,size_tlen);
(13)intstrncmp(charconst*s1,charconst*s2,size_tlen);
功能类似于前3者,但有一些区别和注意点。
见p179
11.无符号数运算的一些注意点:
(1)比较大小时:
if(strlen(x)>=strlen(y))…//合理
if(strlen(x)–strlen(y)>=0)…//不合理
原因:
strlen返回size_t,是无符号整型,相减结果还是无符号(表达式结果保持
了无符号类型),所以永远>=0。
除非把strlen的结果强制转换为int。
12.指针,内存操作中的一些其它注意点:
(1)如下例子:
int*a=0;表达式*a等于多少?
a是一个NULL指针,对它解引用是错误的。
但有些环境不会在运行时捕捉到这个错误,而是去访问内存位置0的内容,这是
一个隐患。
所以,在解引用之前必须先对指针进行有效性检查。
内存操作函数:
void*memcpy(void*dst,voidconst*src,size_tlength);
void*memmove(void*dst,voidconst*src,size_tlength);
void*memcmp(voidconst*a,voidconst*b,size_tlength);
void*memchr(voidconst*a,intch,size_tlength);
void*memset(void*a,intch,size_tlength);
以上函数直接对内存操作,以字节数为单位。
其中,memcpy能处理任意字节的序列,比如将一段内存中的序列读入一个结构体中;而strcpy()只处理字符序列,且遇NUL字节即结束。
memmove与memcpy的唯一区别是:
memmove允许源和目标操作数重叠,memcpy不允许。
memchr从a的起始位置开始查找字符ch第一次出现的位置。
动态分配内存(stdlib.h中):
void*malloc(size_tsize);分配失败返回NULL,所以要作判断。
voidfree(void*pointer);pointer是malloc,calloc,realloc返回值,也可为NULL。
动态分配的内存必须整块一起释放。
void*calloc(sizes_tnum_elements,size_telement_size);与malloc区别在于:
calloc将分配的内存初始化为0。
voidrealloc(void*ptr,size_tnew_size);修改一个原先已经分配好的内存块的大小(长则不足,短则切割,若原先内存块无法改变大小,则分配另一块内存并返回指向它的指针,所以旧的指针就不能再用了)。
若ptr为NULL,则和malloc无异了。
返回类型为void*,可以赋给任意类型的指针。
13.#define宏与函数之间的优劣:
p283
宏的执行速度比函数快得多,函数需要调用、返回等操作。
函数只能对特定的类型操作,而宏是类型无关的,宏还可以实现一些函数无法实现的操作。
但宏需要将所有代码拷贝到调用程序中,增加了代码长度。
所以:
宏比较适合执行简单的计算,如求2个值中的较大值。
如果宏代码比较长,并且频繁被调用,还是声明为函数比较好。
14.预定义符号(由预处理器定义的符号)p279
有_FILE_,_LINE_,_DATE_,_TIME_,_STDC_
15.注意宏使用中的一些细节p283
(1)如下:
#definePRINT(FORMAT,VALUE)\
printf(“Thevalueof”#VALUE);\
“is”FORMAT“\n”,VALUE)
Print(“%d”,x+3);
输出为:
Thevalueofx+3is25
printf()函数的第一个参数必须为字符串,所以比必须在VALUE前面加上#。
#VALUE的目的就是将宏参数VALUE转换为字符串形式。
(2)##的作用:
把两边的符号连接成一个符号。
如下:
#defineADD_TO_SUM(sum_number,value)\
sum##sum_number+=value
ADD_TO_SUM(5,25);//sum5+=25;
(3)宏定义中的参数往往要打上不止一层的括号。
16.一些宏命令:
(1)#undefname将一个宏定义移除(比如一个现存的名字需要被重新定义,那旧定义必须首先被移除)。
P285
(2)条件编译
#ifconstant-expression
statements
#elifconstant-expression
otherstatements
#else
otherstatements
#endif
(3)是否被定义:
p287
#ifdefined(symbol)
#ifdefsymbol
#if!
defined(symbol)
#ifndefsymbol
(4)#error用来生成错误指令。
P291
(5)#line
(6)#progma
17.命令行定义p285
在命令行中定义符号,用来启动编译过程。
如根据同一个源文件编译一个程序的不同版本。
如:
编译时决定数组的长度。
定义数组如下:
intarray[ARRAY_SIZE];
UNIX环境中用-D选项如下:
cc-DARRAY_SIZE=100prog.c;
18.本地形式#include和函数库形式#include的区别:
p289
函数库形式的#include直接在由编译器定义的“一系列标准位置”查找函数库头文件。
本地形式#include现在源文件所在当前目录查找,若未找到,就到标准位置去查找。
19.防止一个头文件在一个源文件中被重复包含,可如下组织头文件:
p291
#ifndefAAA
#defineAAA1
/*
**
*/
#endif
(在比较复杂的系统中,每一个头文件都要这么写。
)
20.函数指针p258
int*f();f是一个函数,返回整型指针。
int(*f)();f是一个函数指针,它所指向的函数返回一个整数值。
int*(*f)();f是一个函数指针,它所指向的函数返回一个整型指针。
intf()[];(错误!
)f是一个函数,它返回一个整型数组,但c的函数返回值只能是标量,不能是数组!
intf[]();(错误!
)f是一个数组,其中的元素是“返回值为整型的函数”,但数组中的元素的长度必须是相同的,而各个函数的长度未必相同。
int(*f[])();f是一个数组,数组元素的类型是函数指针,它所指向的函数的返回值是一个整型值。
int*(*f[])();f是一个数组,数组元素的类型是函数指针,它所指向的函数的返回值是一个整型指针。
(以上为旧式风格的函数声明,应该使用完整的函数说明,如下:
)
int(*f)(int,float);
21.C与c++中的const
第一,C和C++标准都规定了const描述的变量为只读,对const变量进行更改的行为是未定义的,任何结果都是可能的。
对于const变量行为,各个编译器都有自己的理解,在优化的时候极有可能被优化为常量。
这也是为什么很多人推荐使用const变量代替#define常量--既可以被优化成常量,同时还能让编译器进行类型检查。
第二,对于函数参数而言,压栈都是从右往左的。
这应该在标准里提到,我一时没找到。
但各个参数的计算顺序是未定义的,由各编译器自己发挥。
可能会是一边计算一边入栈,也可能是全部计算完再入栈。
常量肯定是只读的,例如5, “abc”,等,肯定是只读的,因为程序中根本没有地方存放它的值,当然也就不能够去修改它。
而“只读变量”则是在内存中开辟一个地方来存放它的值,只不过这个值由编译器限定不允许被修改。
(以上摘自网络)
但c和c++中的
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- c和指针 语言 学习 笔记 结合 教程 指针