C和指针-总结2
1、char *message = " Hello world! "; 等价于 char *message; message = " Hello world! ";
注:字符串是赋给message本身的。
int a,*b;
b = &a;
2、C语言支持typedef的机制,它允许为各种数据类型定义新名字,typedef声明的写法和普通的声明基本相同,只是把typedef这个关键字出现在声明的前面,如:
typedef char *ptr_to_char; 这个声明把标识符 ptr_to_char 作为指向字符指针类型的新名字,然后便可以像使用任何预定义名字一样在下面的声明中使用这个新名字。例如:
ptr_to_char a; 声明 a 是一个指向字符的指针。
使用 typedef 而不是 #define 来创建新的类型名,因为后者无法正确的处理指针类型,例如:
#define d_ptr_to_char char*
d_ptr_to_char a,b;
正确声明了 a ,但是 b 却被声明为一个字符,在定义更为复杂的类型名字时,如函数指针或指向数组的指针,使用 typedef 更为合适。
3、const 关键字可以声明常量,如下声明:
int *pi; pi 是一个普通的指向整型的指针
int const *pci; 指向整型常量的指针,可以修改指针的值,但不能修改它所指向的值。
int *const pci;声明 pci 为一个指向整型的常量指针,此时指针式常量,它的值无法修改,但可以修改它所指向的整型的值。
int const * const pci; 无论是指针还是它所指向的值都是常量,不允许修改。
4、#define 指令是另一种创建名字常量的机制,例如:
#define MAX_ELEMENTS 50
int const max_elements = 50;
在这种情况下,使用 #define 比使用 const 变量更好,只要允许使用字面值常量的地方都可以使用前者,比如声明数组的长度,const 变量只能用于允许使用变量的地方,名字常量维护性高,修改容易。
5、作用域:
代码块作用域:位于一对花括号之间的所有语句称为一个代码块,任何在代码块的开始位置声明的标识符具有代码块作用域,表示它们可以被这个代码块中的所有语句访问。当代码块处于嵌套状态时,声明于内层代码块的标识符的作用域到达该代码块的尾部便告终止,然而,如果内层代码快有一个标识符的名字与外层代码块的一个标识符同名,内层的那个标识符就将隐藏外层的标识符——外层的那个标识符无法再内存代码块中通过名字访问,声明9的f和声明6的f是不同的变量,后者无法在内层代码块中通过名字来访问。 故应该避免在嵌套的代码块中出现相同的变量名。 不是嵌套的代码块则稍有不同,声明于每个代码块的变量无法被另一个代码块访问,因为它们的作用域并无重叠之处。由于两个代码块的变量不可能同时存在,所以编译器可以把它们存储于同一个内存地址,因为任意时刻,两个非嵌套的代码块最多只有一个处于活动状态。
文件作用域:任何在所有代码块之外声明的标识符都具有文件作用域,它表示这些标识符从它们的声明之处直到它所在的源文件结尾处都是可以访问的,如1和2.在文件中定义的函数名也具有文件作用域,函数名本身并不属于任何代码块(如:声明4).
1 int a;
2 int b ( int c );
4 int d ( int e )
{
6 int f;
7 int g( int h );
.........
{
9 int f,g,i;
....
}
{
10 int i;
.......
}
}
6、链接属性:external、internal、none,没有链接属性的标识符总是被当做单独的个体,也就是说该标识符的多个声明被当做独立不同的实体;属于internal链接属性的标识符在同一个源文件内的所有声明中都指同一个实体,但位于不同源文件的多个声明则分属不同的实体;最后,属于external链接属性的标识符不论声明多少次、位于几个源文件都表示同一个实体。如果某个声明在正常情况下具有external链接属性,在它面前加上 static 关键字可以使它的链接属性变为internal。
static 关键字:当用于函数定义时,或用于代码块之外的变量声明时,static 关键字用于修改标识符的连接属性,从 external 改为 internal,但标识符的存储类型和作用域不受影响,但标识符的存储类型和作用域不受影响,用这种方式声明的函数或变量只能在声明它们的源文件中访问。当它用于代码块内部的变量声明时,static 关键字用于修改变量的存储类型,从自动变量修改为静态变量。
static int b; 变量b就将为这个源文件所私有,不会被其他源文件调用。
extern 关键字的规则更为复杂,它为一个标识符指定external链接属性,这样就可以访问在其他位置定义的这个实体。
7、属于文件作用域的声明在缺省情况下为external链接属性,所以第一行的a的链接属性为external,static链接属性为internal,变量a,b,c的存储类型为静态,表示它们并不是存储于堆栈中,因此,这些变量在程序执行之前创建,并一直保持它们的值,直到程序结束。当程序开始执行时,变量a将初始化为5。
1 int a = 5;
2 extern int b;
3 static int c;
4 int d( int e )
5 {
6 int f = 15;
7 register int b;
8 static int g = 20;
9 extern int a;
10 ............
11 {
12 int e;
13 int a;
14 extern int h;
15 .........
16 }
17.........
18 {
19 int x;
20 int e;
21 .........
22 }
23 .......
24 }
25 static int i1;
26 {
27 ..........
28 }
29 .............
8、当 extern 关键字用于源文件中一个标识符的第 1 次声明时,它指定该标识符具有 external 链接属性。但是,如果它用于该标识符的第 2 次或以后的声明时,它并不会更改又第 1 次声明所指定的链接属性,如:声明 4 并不修改由声明 i 的链接属性。
1 static int i;
int func()
{
2 int j;
3 extern int k;
4 extern int i;
.........
}
9、总结:具有 external 链接属性的实体在其他语言的术语里称为全局实体,所有源文件的所有函数均可以访问它,只要变量并非声明于代码块或函数定义内部,它在缺省情况下的链接属性即为 external 。如果一个变量声明于代码块内部,在它前面添加 extern 关键字将使它所引用的使全局变量而非局部变量。
全局变量在程序开始执行前创建,并在程序整个执行过程中始终存在。从属于函数的局部变量在函数开始执行时创建,在函数执行完毕后销毁,但用于执行函数的机器指令在程序的生命期内一直存在。
10、当 if 语句嵌套出现时,就会出现“悬空的 else ”问题。例如:
if( i > 1 ){
if( j > 2 )
printf(" i > 1 and j > 2\n");
else
printf("no they're not \n");
else 子句从属于最靠近它的不完整的 if 语句,若想让它从属于第一个 if 语句,可以把第二个 if 语句补充完整,加上一条空的 else 子句,或者用花括号把它包围在一个代码块之内,如下所示:
if( i > 1 ){
if( j > 2 )
printf(" i > 1 and j > 2\n");
}
else
printf("no they're not \n");
11、break 和 continue 语句:在 while 循环中可以使用 break 语句,用于永久终止循环;在 while 循环中也可以使用 continue 语句,它用于永久终止当前的那次循环,在执行完 continue 语句之后,执行流接下来就是重新测试表达式的值,决定是否继续执行循环。
这两条语句的任何一条如果出现于嵌套的循环内部,它只对最内层的循环起作用,你无法使用 break 或 continue 语句影响外层循环的执行。
如果循环体内执行了 continue 语句,循环体内的剩余部分便不再执行,而是立即开始下一轮循环,当循环体只有遇到某些值才会执行的情况下,comtinue 语句相当有用。
12、switch 语句,每个 case 标签必须具有一个唯一的值,case 标签并不把语句划分为几个部分,只是确定列表的进入点。break语句起到划分的作用。如果在 switch 语句的执行中遇到了 break 语句,执行流就会立即跳到语句列表的末尾。在最后一个 case 语句后面加上一条 break 语句,是为了以后维护方便。万一以后再添加一条 case,可以避免出现在以前的最后一个 case 语句后面忘了添加 break 语句。在每个 switch 语句中放上一条 default 子句是个好习惯,这样了以检测到任何非法值。
偶尔也需要让执行流从一个语句组贯穿到下一个语句组,如:
switch( ch ){
case '\n':
lines += 1;
/*FALL TRUE*/
case ' ':
case '\t':
words += 1;
/*FALL TRUE*/
default:
chars += 1;
}
这个程序计算输入行、单词、字符的个数,每个字符都必须计数,但空格和制表符同时也作为单词的终止符使用。功能:换行符增加所有三个计数器的值,空格和制表符增加两个计数器的值,而其余所有的字符都只增加字符计数器的值。
加注释很重要,以后再维护程序时可能会节省很多时间,/*FALL TRUE*/,若没有这个注释,一个不够细心的寻找 bug 的维护程序员可能会觉得这里缺少 break 语句是个错误,就是 bug 的根源,误以为这是个错误。
11、goto语句:使用 goto 语句在跳出多层嵌套的循环非常合适。一般情况下,避免 goto 语句。
12、一个错误: x = get_some_value();
if(x = 5)
执行某些任务
x从函数获得一个值,但我们把 5 赋值给 x,而不是把 x 与字面值 5 进行比较,从而丢失了从函数获得的那个值,这个结果显然不是程序员的意图所在。但是,这里还存在另外一个问题,由于表达式的值是 x 的新值(非零值),所以 if 语句始终为真。故在进行相等性测试比较时,检查书写的确实是双等号符。
13、expression1 ? expression2 : expression3
expression1 值为真,执行 expression2,否则执行 expression3
14、如: if( a > 5)
b = 3;
else
b = -20;
相当于: b = a > 5 ? 3 : -20;
再如:
if(a > 5)
b [ 2 * c + d ( e / 5 ) ] = 3;
else
b [ 2 * c + d ( e / 5 ) ] = -20;
在这里,长的下标表达式写两次,麻烦,使用条件操作符,清晰明了!
b [ 2 * c + d ( e / 5 ) ] = a > 5 ? 3 : -20;
15、算数转换:如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另外一个操作数的类型,否则操作就无法进行,下面的层次体系称为寻常算数转换。
long double . double . float . unsigned long int . long int . unsigned int . int .
如果某个操作数的类型在上面这个列表中排名较低,那么它首先将转化为另外一个操作数的类型然后执行操作。
如下代码,包含一个人潜在问题:
int a = 5000;
int b = 25;
long c = a * b;
问题在于表达式 a*b 是以整型进行计算,在32位整数的机器上,这段代码运行起来毫无问题,但在16位整数的机器上,这个乘法运算会产生溢出,这样 c 就会被初始化为错误的值。解决方案是在执行乘法运算之前把其中一个(或两个)操作数转换为长整型。 long c = ( long )a * b;
16、位操作符的原理及用途:& , | , ~ , ^ , >> , << .
按位与:作为关闭某位(即将该位置0)的手段,例如关闭 a 数中的第 3 位,而又不影响其他位的现状,可以用一个数 0xf7,即二进制1110111去与 a数作按位“与”运算,a &= (0XF7)。
按位或:作为置位的手段,例如将 a 数中的第 0 位和 1 位置 1,而又不影响其他位的现状,可以用一个数 0x03,与 a 数作按位“或”运算,a |= (0XF7)来表达。
按位异或:可以使特定的位取反
直接交换两个变量的值:例如,若有变量 a = 3,b = 4,想要交换 a 和 b 的值,做如下操作,
a ^= b;
b ^= a;
a ^= b;
首先 a ^= b,得 a = 00000011 ^ 00000100 = 00000111;b ^= a,得 b = 00000100 ^ 00000111 = 00000011; a ^= b,得 a = 00000111 ^ 00000011 = 00000100。
这样两个变量中的值就进行了对调。(此操作比用临时变量的方式高效很多)
取反运算:将各位数字取反。
数据右移和数据左移:移位操作可用于整数的快速乘除运算,左移一位等效于乘2,右移一位等效于除2.
16、for语句:for(expression1; expression2; expression3 )
statement;
其中 statement 称为循环体, expression1 为初始化部分,它只在循环开始时执行一次,expression2称为条件部分,它在循环体每次执行前都要执行一次,都像 while 语句中的表达式一样,expression3称为调整部分,它在循环体每次执行完毕,在条件部分即将执行之前执行,所有三个表达式都是可选的,都可以省略,若省略条件部分,表达测试的值始终为真。
相当于: expression1:
while( expression2 ) {
statement
expression3;
}
当没有初始化部分和调整部分的表达式时,while 语句比使用 for语句更加合适。
17.getchar()用法:单个字符的输入函数。
头文件: #include <stdio.h>
函数 getchar() 用于从标准输入控制台读取字符,原型如下:int getchar(void);
[参数]该函数没有参数。
[返回值]函数的返回值为用户输入的第一个字符的 ASCII 码,若出错返回 -1,且将用户输入的字符回显到屏幕。只能用于单个字符的输入,一次输入一个字符,程序的功能是输入一个字符显示一个字符,回车换行,再输入并显示一个字符。而运行时字符是连续输入的,运行结果却是正确的。如果用户在按回车键之前输入了不止一个字符,其他字符会保留在键盘缓冲区中,等待后续 getchar() 调用读取。也就是说,后续的 getchar() 调用不会等待用户按键,而是直接读取缓冲区中的字符,知道缓冲区中的字符读取完毕后,才等待用户按键。
getch不用按回车键。
#include < stdio.h >
void main()
{
int c;
//从控制台流中读取字符,直到按回车键结束
while( ( c = getchar()) != '\n' )
{
printf("run here \n");
printf("%c\n",c);
}
printf("\n");
}
putchar():单个字符输出函数,格式: putchar(ch),其中 ch 可以是一个字符变量或常量,也可以是一个转义字符。作用:向终端输出一个字符,只能用于单个字符的输出,且一次只能输出一个字符。在开头要加上编译预处理命令 include "stdio.h".
#include <stdio.h>
#include <stdlib.h>
int main()
{
char ch1 = 'N',ch2 = 'C',ch3 = 'B';
putchar(ch1);
putchar(ch2);
putchar(ch3);
putchar('\n');
putchar('N');
putchar('\n');
putchar(ch2);
putchar('\n');
putchar(ch3);
putchar('\n');
}
运行结果:
NCB
N
C
B
又如:
#include <stdio.h>
#include <stdlib.h>
int main()
{
char ch;
printf("please input a series of words.\n");
ch = getchar();
putchar(ch);
putchar('\n');
putchar(getchar());
putchar('\n');
putchar(ch);
putchar('\n');
putchar(ch);
putchar('\n');
}
如果输入 aaaaaaaa
运行结果:
a
a
a
a
18、当需要循环体至少执行一次时,选择 do。
}

浙公网安备 33010602011771号