C与指针、C陷阱与缺陷

遗留问题:
P15理解函数声明
P39指针与数组

1、单/多字符符号—->贪心法
a+++++b?

【先了解a++与++a的实现原理】

//首先对于i++的实现是:(此时返回的是临时对象)
    int   temp;  
    temp   =   i;  
    i   =   i+1;  
    return   temp;  
//而++i的实现是:  
    i   =   i+1;  
    return   i;   

含义:等价于((a++)++)+b,但是编译失败,因为((a++)++)+b中先执行a++返回的结果不能作为左值,因此编译失败。

【总结】i++不能作为左值,++i可以作为左值
再例:
int main()
{
int d = 0,x = 0;
d = x+++2; //执行结果等价于 d = x+2; x++;
x++ = 2; //编译失败,x++不能作为左值
++x = 2; //编译成功,++x能作为左值
}

2、
单引号:引起来的一个字符实际上代表一个整数(整数值对应于该字符在编译器采用的字符集中的序列值)。
双引号:引起来的字符串代表一个指向无名数组起始字符的指针。
3、

int a = 010;   //表示8进制的数。

4、运算符的优先级和结合性
【注】
自右向左的运算符:单目运算符、条件运算符?:、赋值运算符=
优先级顺序:单目、算数、移位、关系(比较)、位、逻辑、三目、赋值、逗号。
5、多写分号

    if(a==0);    //此处多了一个分号,编译成功
        a=1;
    //等价于下面的语句
    if(a==0)  {}
        a=1;

6、if-else配对问题
else始终与同一对括号内最近的未匹配的if结合。
7、C语言允许初始化列表出现多余的逗号。例如:int days[] = {1,2,3,};
作用:据说初衷是为了方便自动代码生成工具, 实际上可以说是一处设计失误,代码自动生成可以用啊。 你可以写一个代码,帮你自动生成源代码。 这时候,就不用考虑各种格式问题,每一次输出都是 “%d, “这种类型咯。
8、数组名a与&a
【注】一个数组只需要知道两点:数组的大小,数组的首地址。

//(一)
int a[] = {0,1,2};
int *p = &a;  //编译错误
int *p = a;   //正确
因为p是一个指向int类型的指针,类型是int*;
而&a表示指向数组的指针,类型是int (*)[3]。
它们类型不匹配! 

//(二)
//a+1表示的是数组第一个元素地址,&a+1表示的是跨过a数组的下一个地址
其实&a和a的值倒是完全一样的,都是下标为0的元素的地址,关键在于它们的作用域不同!(如图下所示)

这里写图片描述
(三)多维数组指针的定义

int array[12][31];
int (*P)[31] = array; //True
int *p = array;    //  无法从“int [12][31]”转换为“int *”
int *p = &array;   // 无法从“int (*)[12][31]”转换为“int *”

9、malloc给数组分配多大的空间?
char *r = malloc(strlen(array)+1); //注:此处必须加1.
10、数组名做形参,则数组名会立刻被转化为指向给数组第一个元素的指针。
也就是说,下面两种写法等价:

int strlen(char s[])
{

}
int strlen(char *s)
{

}

再例:

void str(int s[])
{
    cout<<sizeof(s)<<endl;   // 4 因为数组名退化成指针
    cout<<s[0]<<endl;   // 1,但是下标访问[]还可以使用
}
int main()  
{  
    int array[12] = {1,2,3};
    str(array);
    return 0;  
} 
    char st[] = "Hello";
    printf("%s",st);  //打印Hello,因为printf中的数组名st做参数,退化成指针。

11、

//case 1:
int i = 0;
while(i < n)
    y[i] = x[i++];
//case 2:
int i = 0;
while(i < n)
    y[i++] = x[i];
//case 3:
int i = 0;
while(i < n)
{
    y[i] = x[i];
    i++;
}
//case 4:
for(int i = 0;i<n;i++ )
    y[i] = x[i];
/*   case 1、2是错误写法,不要这么写!
     case 3、4正确!
【注】求值顺序在作怪。
*/
12、整数溢出
对于加法运算,怎么进行溢出检查,有下面两种形式:

INT_MAX是已定义的最大整数值。
//①
if((unsigned)a+(unsigned)b > INT_MAX)
complain();
//②
if(a > INT_MAX - b)
complain();

12extern关键字
**在一个源文件里定义了一个数组:char a[6];**
**在另外一个文件里用下列语句进行了声明:extern char *a;这样可以么?**
不可以,程序运行时会告诉你非法访问。原因在于,指向类型T的指针并不等价于类型T的数组。extern char *a声明的是一个指针变量而不是字符数组,因此与实际的定义不同,从而造成运行时非法访问。应该将声明改为extern char a[ ]
【结论】这提示我们,在使用extern时候要严格对应声明时的格式,在实际编程中,这样的错误屡见不鲜。
13、命名冲突与static修饰符
static修饰符在C语言中,与extern作用相反,它把变量/函数封装在一个.cpp文件中,使之对外界.cpp文件不可见。
14

//读入、处理、打印剩余的行
while(get(input)!=NULL)
{
printf(“%s”,input);
}

【介绍gets函数】
gets从标准输入设备读字符串函数。可以无限读取,不会判断上限,以回车结束读取,所以程序员应该确保buffer的空间足够大,以便在执行读操作时不发生溢出。
从stdio流中读取字符串,直至接受到换行符或EOF时停止,并将读取的结果存放在buffer指针所指向的字符数组中。换行符不作为读取串的内容,读取的换行符被转换为‘\0’空字符,并由此来结束字符串。
读入成功,返回与参数buffer相同的指针;读入过程中遇到EOF(End-of-File)或发生错误,返回NULL指针。所以在遇到返回值为NULL的情况,要用ferror或feof函数检查是发生错误还是遇到EOF。
【printf格式化输出】
%o八进制     %x十六进制    %g浮点型数据

15、
char: 0~127
signed char: -127~127
unsigned char: 0~255

八进制: 0开头
十六进制: 0x开头

16、左移右移位运算符
对于unsigned值执行所有的移位操作符都是逻辑移位(即:补0)。
17、宏定义中的空格

 #define  f  (x)  ((x)-1)  //中间有空格
 下面有两种说法,哪种说法正确:
 ①f(x)代表((x)- 1--->错误
 ②f代表(x)((x)- 1--->正确

18、宏定义中用好括弧并不能保证“万无一失”
例如:
#define max(a,b) ((a)>(b)?(a):(b)
c = ((a)>(b++)?(a):(b++));
//分析:此时b会被相加两次,造成意想之外的结果,宏出现副作用。
【解决方案】—>用函数代替宏
【总结】在使用宏的时候,如果宏定义中同一个变量出现两次,那么在使用宏时如果出现++或者–,就容易发生“副作用”,我们要避免这种情况的发生。
19、从用户态切换到系统态的三个要素:
异常、中断、系统(内核函数)调用
20、main函数正常退出返回0,异常退出返回-1.
21、gets( 字符数组名 ):把输入设备中的一行字符串存储到字符数组中
从“标准输入”读取“一行”文本并把它存储在字符数组中。一行输入由一串字符组成,以一个换行符结尾。gets函数丢弃换行符,并在该行的末尾存储一个NUL字节(一个NUL字节是指字节模式为全0的字节,类似与’\0’这样的字符常量)。
返回非NULL表示读取成功;
当读入过程遇到EOF或发生错误时,返回NULL。

char input[MAX_INPUT];
while(gets(input)!=NULL)
    printf("%s\n",input);

【拓展】
puts(string):把字符串string输出到屏幕
puts()函数用来向“标准输出设备(屏幕)”写字符串并换行, 其调用格式为: puts(s);
puts(s) 等效于printf(“%s\n”,s),前提 :s是C风格字符串,最后以’\0’结尾。

说明:
(1). puts()函数只能输出字符串, 不能输出数值或进行格式变换。 
(2). 可以将字符串直接写入puts("字符串")函数中。如: puts("Hello World");
#include <stdio.h>
#include <conio.h>
int main(void)
{
int i;
char string[20];
for(i=0;i<10;i++)
string[i]='a';
puts(string);
getch();
return 0;
}
输出结果:aaaaaaaaaa烫烫烫烫
从此例中可看到puts输出字符串时要遇到'\0’也就是字符结束符才停止

22、getchar()

int ch;  //注意:char ch会出错
while((ch = getchar())!=EOF)
    ... ...

23、间接运算符 ->和 . (用于访问变量中指向的的内容的值)
24、指针几种常见的错误用法

int *p;  //
*p = 100;  //非法进行访问未初始化指针变量p中的内容NULL指针解引用&

假设变量a存储的位置为100
*100 = 25//错误用法
*(int*)100 = 25;//正确用法:强制把值100从“整型”转换为“指向整型的指针”

*p++的含义  //不是(*p)++,而是*(p++),因为优先级:后增 > *

25、如果传递的参数是数组名,并且在函数中使用下标引用[ ]该数组的参数,那么在函数中对数组元素进行修改实际上修改的是调用程序中的数组元素。(函数将访问调用程序的数组元素,数组不会被复制),这种行为相当于“传址调用”。
26、一维数组名的含义
数组名:是指针常量,即数组第一个元素的地址&array[0],它不能被改变,不能被赋值。

如何区分字符数组和字符串常量
char  ch[ ] = "Hello World"; //字符数组
char  *ch   = "Hello World"; //字符串常量
[例1]
int a[3] = {1,1,1,1}; //编译失败错误  error C2078: 初始值设定项太多
[例2]
#include <iostream>
using namespace std;
int main(void)
{
    int a[3] = {1,1,1};
    for(int i=0;i <=3;i++)
        cout<<a[i]<<endl;
    return 0;
}
//运行结果:1,1,1,-12283912145   [注]:数组的访问越界没法被检查,因为没有明确的准则。

int a[10];
int* p = &a[4];
p[-2] ——> 的含义是什么? //p[-2]等于a[2],分析:偏移量为-2,(在当前p指向的基础上,向左偏移2个位置)
p[2]  ——> 的含义是什么?//p[2]等于a[6],分析:偏移量为+2,(在当前p指向的基础上,向右偏移两个位置)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值