[C和指针]第一部分
第一章 快速上手... 1
第二章 数据... 2
第三章 操作符... 6
第一章 快速上手
#if 0
statements
#endif
#include <stdio.h>:这是一种预处理指令,由预处理器解释。预处理器使用stdio.h的库函数头文件的内容替换预处理指令,其结果就像stdio.h的内容被逐字写到源文件的那个位置。另一种预处理指令 #define 像Java中定义的public final static int XX 定义的静态的final常量。
如果你有一些声明需要被几个不同的源文件使用,你可以把这些声明编写在一个头文件中,然后在需要使用的源文件使用 #include 预处理指令来包含这些声明,这样就只需要一份拷贝,便于后期的维护。
函数原型便于编译器在调用它们进行准确性检查。
假如某个程序的代码由几个源文件所组成,那么使用其他源文件的源文件需要写明被使用源文件中函数的原型。把原型放在头文件中并使用#include指令包含它们,可以避免多份拷贝。
数组名就是第一个元素的地址,一般不需要在数组名前加上 & ,但你也可以加上也是没有问题的。
。如果正常读取,则返回个数1。
gets函数从标准输入读取一行文本并把它存储在一个字符数组中,它会丢弃换行符,并在该行的结尾加上一个空字符 \0。
puts函数是gets函数的输出版本,它把指定的字符串写到标准输出并在末尾添上一个换行符。
getchar函数从标准输入读取一个字符并返回它的值,如果输入中不再存在任何字符,函数就会返回常量EOF(-1,在stdio.h中定义),用于提示文件的结尾。getchar函数返回的是字符的ASCII码整数。
putchar将整型变量的内容以字符的形式打印出来,通常显示在屏幕上
C运行过程
连接程序把目标代码和库函数的代码连接起来产生可执行的映象。
在基于UNIX的C系统中,编译和连接程序的命令是cc,如编译和连接welcome.c的程序,在UNIX提示符下键入:
cc welcome.c
就会产生一个名为a.out的文件,该文件是程序welcome.c的可执行映象,在命令提示符中输入a.out就可以执行程序了。
第二章 数据
ANSI标准则加入了一个规范,说明了各种整型值的最小允许范围:
类型 | 最小范围 |
char | 0到127 |
signed char | -127到127 |
unsigned char | 0到255 |
short int | -32767到32767 |
unsigned short int | 0到65535 |
int | -32767到32767 |
unsigned int | 0到65535 |
long int | -2147483647到2147483647 |
unsigned long int | 0到4294967295 |
limits.h文件中说明了各种不同整数类型特点:
#define CHAR_BIT 8 //字符的位数(至少8位)
#define SCHAR_MIN (-128) //signed char最小值
#define SCHAR_MAX 127 //signed char最大值
#define UCHAR_MAX 255 //unsigned char最大值
#if ('\x80' < 0) //看一个字节能否能够存储 -128,如果能够,则缺省的字符就是有符号的,否则就是无符号的,一般缺省的char为有符号字符
#define CHAR_MIN SCHAR_MIN
#define CHAR_MAX SCHAR_MAX
#else
#define CHAR_MIN 0
#define CHAR_MAX UCHAR_MAX
#endif
#define SHRT_MAX 32767 //short int最大值
#define SHRT_MIN (-SHRT_MAX-1) //short int最小值
#define USHRT_MAX 0xffff //unsigned short int最大值
#define INT_MAX 2147483647 //int最大值
#define INT_MIN (-INT_MAX-1) //int最大值
#define UINT_MAX 0xffffffff //unsigned int最大值
#define LONG_MAX 2147483647L //long int最大值
#define LONG_MIN (-LONG_MAX-1) //long int最大值
#define ULONG_MAX 0xffffffffUL //unsignedlong int最大值
种不同类型中的哪一种呢?答案取决于字面值是如何书写的,但是你可以在有些字面值的后面添加一个后缀来改变缺省的规则。L为long整型值,U用于把数值指定为unsigned整型值。
十进制整型字面值可能是,NULL指一个其值为0的指针。符号NULL在头文件stdio.h中定义,另一方面,并不存在预定义的符号NUL,所以如果你想使用它而不是字符常量'\0',你必须自行定义。
在 K&R C中,没有提及一个字符串常量中的字符是否可以被程序修改,但清楚地表明具有相同的值的不同字符串常量在内存中是分开存储的。因此,许多编译器都允许程序修改字符串常量;但ANSI C则声明如果对一个字符串常量进行修改,其结果是未定义的,它把一个字符串常量存储于一个地方,即使它在程序中多次出现,所以在程序中最好不要修改字符串常量。
int* a, b, c;
人们很自然地以为这条语句把所有三个变量声明为指向整型的指针,但事实上并非如此,星号实际上是表达 *a 的一部分,只对这个标识符有用,其他两个变量则只是普通的整型。
char *message = "Hello world!";
这条语句把message声明为一个指向字符的指针,并用字符串常量中第一个字符的地址对该指针进行初始化。这里看上去初始化似乎是赋给表达式 *message,事实上它是赋给message本身的。换句话说,前面一个声明相当于:
char *message;
message = "Hello world!";
隐式声明:C语言中有几种声明,它的类名可以省略。例如,函数如果不地声明返回类型,它就默认返回整型。使用旧网格声明函数的形式参数时,如果省略了参数的类型,编译器就默认它们为整型。最后,如果编译器可以推断出一条语句实际上是一个声明时,如果它缺省类型名,编译器会假定它为整型:
int a[10];
int c;
b[10];
d;
f( x) {
return x + 1;
}
上面只能在 K&R C 标准通过编译,在ANSI C是非法的。
常量定义的两种方式:
#define MAX_LENGTH 10
intconst max_length = 10;
一旦定义并初始化后,都不能修改其值,但是第一种更好。
4种不同的作用域:文件作用域、函数作用域、代码块作用域、原型作用域。
位于一对花括号之间的所有语句称为一个代码块。任何在代码块的开始位置声明的标识符都具有代码块作用域。如果代码块处于嵌套状态,并且内层以代码块有一个标识符的名字与外层相同,则内层的那个标识符会隐藏外层的标识符。但是,如果在函数体内部声明了名字与形参相同的局部变量(在同一层,如果定义在嵌套内层则还是会隐藏),ANSI C不会隐藏,编译时会出错:
void func(int i ){
//!int i;//编译出错,因为这两个i在同一层定义
{
int i;//这属性嵌套定义,会隐藏外层相同定义
}
}
任何在所有代码块之外的标识符都具有文件作用域,它从声明之处到源文件结尾处都是可以访问的。种:external(外部)、internal(内部)、none(无)。没有链接属性的标识符(none)总是被当作单独的个体,也就是说该标识符的多个声明被当作不同的实体。属于internal链接属性的标识符在同一个源文件内的所有声明中都指同一个实体,但位于不同源文件的多个声明则分属不同的实体。最后,属于external链接属性的标识符不论声明多少次、位于几个源文件都表示同一个实体:
/*1*/typedefchar *a;
/*2*/int b;
/*3*/int c(int d /*4*/) {
/*5*/int e;
/*6*/int f(/*7*/int g);
}
在缺省情况下,标识符b、c和f的链接属性为external(这里的意义应该是可以被外部其他源文件访问,而external关键本身的意义是引用其他源文件中的变量或函数),其余标识符的链接属性则为none。关键字extern和static用于在声明中修改标识符的链接属性,如果某个声明在正常情况下具有external链接属性,在它前面加上static 关键字可以使它的链接属性变为internal。static只对缺省链接属性为external的声明才有改变链接属性的效果。例如,尽管你可以在声明5前面加上static关键字,但它的效果完全不一样,因为e的缺省链接属性并不是external。
函数代码就是存储在静态内存中。
声明3为k指定external链接属性,这样函数就可以访问在其他源文件声明的外部变量了:
/*1*/staticint i;
int func() {
/*2*/int j;
/*3*/externint k;
/*4*/externint i;
}
当external关键字用于源文件中一个标识符的第一次声明时,它表示该标识符具有external链接属性,但如果它用于该标识符的第二次或以后的声明,它并不会更改由第一次声明所指定的链接属性,上面的声明4并不修改由声明1所指定的变量i的链接属性。
凡是在任何代码块之外声明的变量总是存储于静态内存中,不在堆栈内存,这类变量称为静态(static)变量。
在代码块内部声明的变量的缺省存储类型是自动的,也就是说它存储于堆栈中,称为自动(auto)变量。
函数的形式参数不能声明为静态的,因为实参总是在堆栈中传递给函数。
关键字register可以用于自动变量的声明。
static:
当它用于函数定义时,或用于代码块之外的变量声明时,static关键字用于修改标识符的链接属性,从external改为internal,但标识符的存储类型和作用域不受影响。用这种方式声明的函数或变量只能在声明它们的源文件中访问。
当它用于代码块内部的变量声明时,static关键字用于修改变量的存储类型,从自动变量修改为静态变量,但变量的链接属性和作用域不受影响。该变量在运行期一存在。
第三章 操作符
标准说明无符号值执行的所有移位操作都是逻辑移位,但对于有符号值,是逻辑还是算术取决于编译器,需要写一个程序测试一下。因此,一个程序如果使用了有符号数右移操作,它就是不可移植的。
下面这段代码是错误的:
char ch;
while((ch=getchar())!=EOF){...}
EOF需要的位数比字符型值所能提供的位数要多,这也是getchar返回一个整型值而不是字符值的原因。然而,把getchar的返回值首先存储于ch中将导致它被截断。然后这个被截断的值被提升为整型并与EOF进行比较。当这段代码在使用有符号字符集的机器上运行时,如果读取了一个值为\377的字节时,循环将会终止,因为这个值截断再提升之后(提升时补符号位)与EOF相等。当这段代码在使用无符号字符集的机器上运行时(提升时补0),这个循环将永远不会终止。
由于C语言中的char可以是有符号的,所以与Java是不同的,Java中的字符永远是无符号的,,则扩展为,如果为零,则扩展为);如果它是,那么整个表达式的值就为真。
下标引用和间接访问表达式是等价的:
arry[下标]
*(arry + (下标))
下标引用实际上是以后面这种形式实现的。
.和->操作符用于访问一个结构的成员。如果s是个结构变量,那s.a就是访问s中名叫a的成员。当你拥有一个指向结构的指针而不是结构本身,且想访问它的成员时,就需要使用->操作符而不是.操作符。
零是假,任何非零值皆为真。如果flag的值只是0或1,则下面每两对中的两个语句是等价的:
#defined FALSE 0
#defined TRUE 1
...
if(flag == FALSE)...
if(!flag)...
...
if(flag == TRUE)...
if(flag)...
但是,如果flag设置为任意的整型值,那么第2对语句就不是等价的了,可以显示地对它进行测试来解决这个问题:
if(flag != 0)...
if(flag)...
位整数的机器上,这段代码运行起来没有问题,但在16位整数的机器上,这个乘法运算会产生溢出。解决方案是将其中任何一个或两个强转为long:
long c = a * (long)b;
两个个乘法运算将在所有加法运算之前进行,但事实上不是这样的,实际上只保证每个乘法运算在它相邻的加法运算之前执行即可这个表达式可能按下面顺序进行:
a 个加法运算在最后一个乘法运算之前执行,但最终的结果是一样的。所以优先级只对相邻操作符的执行顺序起作用,这里并没有任何规则要求所有的乘法运算首先进行,也没有规则规定这几个乘法运算之间谁先执行。
f() + g() + h(),尽管左边那个加法运算必须在右边那个加法运算之前执行,但对于各个函数调用的顺序,并没有规则加以限制。为了避免副作用,请使用临时变量先调用。
c + --c是危险的:操作符的优先级规则要求自减运算在加法运算之前进行,但我们,c得到的值为11
d = b++;//b增加至11,但d得到的值仍为10
前缀和后缀形式的增值操作符都自制一份变量值的拷贝,用于周围表达式(上面指赋值操作)的值正是这份拷贝。前缀操作符在进行复制之前增加变量的值,后缀操作符在进行复制之后才增加变量的值。这些操作的结果不是被它们所修改的变量的值,而是变量值的拷贝,搞清这一点非常重要。
优先级决定表达式中各种不同的运算符起作用的优先次序,而结合性则在相邻的运算符的具有同等优先级时,决定表达式的结合方向。
操作符 |
描述 |
结合性 |
() [] . -> |
():函数调用(也可用来改变表达式的优先级)[]:数组下标 .:通过结构体变量访问成员变量 ->:通过结构体指针访问成员变量 |
left-to-right |
++ -- + - ! ~ (type) * & sizeof |
++:前/后自增 --:前/后自减 +:一元正 -:一元负 !:逻辑取反/按位取反 (type):类型转换 *:间接访问 &:取地址 sizeof:确定类型或表达式所在字节数 |
right-to-left |
* / % |
乘/除/取模 |
left-to-right |
+ - |
加/减 |
left-to-right |
<< >> |
左移位,右移位 |
left-to-right |
< <= > >= |
小于/小于等于/大于/大于等于 |
left-to-right |
== != |
等于/不等于 |
left-to-right |
& |
按位与 |
left-to-right |
^ |
位异或 |
left-to-right |
| |
按位或 |
left-to-right |
&& |
逻辑与 |
left-to-right |
|| |
逻辑或 |
left-to-right |
?: |
条件操作符 |
right-to-left |
= += -= *= /= %= &= ^= |= <<= >>= |
=:赋值 +=:加赋值 -=:减赋值 *=:乘赋值 /=:除赋值 %=:模赋值 &=:位与赋值 ^=:位异或赋值 |=:位或赋值 <<=:位左移赋值 >>=:位右移赋值 |
right-to-left |
, |
逗号 |
left-to-right |
说明,同一行的优先级是相同的
int main(int argc, char **argv) {
int i[] = { 3, 5 };
int *p = i;
//先拷贝p,再让p下移,再取拷贝p中值,
//值减一后再拷贝,最后将值拷贝赋给j
int j = --*p++;
printf("%d\n", j);//2
printf("%d", *p);//5
}
(一)a = b = c;
关于优先级与结合性的经典示例之一就是上面这个“连续赋值”表达式。
b的两边都是赋值运算,优先级自然相同。而赋值表达式具有“向右结合”的特性,这就决定了这个表达式的语义结构是“a = (b = c)”,而非“(a = b) = c”。即首先完成c向b的赋值,然后将表达式“b = c”的值再赋向a。
一般来讲,对于二元运算符▽来说,如果它是“向左结合”的,那么“x ▽ y ▽ z”将被解读为“(x ▽ y) ▽ z”,反之则被解读为“x ▽ (y ▽ z)”。注意,,大于2的条件成立,从而使第二个条件运算取得结果“20”;然后再来求值整个条件表达式。这时,由于y已经变成3,“x > y”不再成立。整个结果自然就是刚刚求得的20了。
这种思路是错误的。
错误的原因在于:它把优先级、结合性跟求值次序完全混为一谈了。
首先,。也因此冒号之后的部分得不到求值机会,它的所有副作用也就没机会起作用。
为了可移植性,尽量使用函数库。
请编写函数:
unsignedint reverse_bits(unsignedint value)
这个函数的返回值把value进行翻转,如在32机器上,25这个数的二进制如下:
00000000 00000000 00000000 00011001
则函数返回的值应该为2550136832,它的二进制位模式为:
10011000 00000000 00000000 00000000
/*
* 将一个无符号整型进行翻转
*/
unsignedint reverse_bits(unsignedint value) {
unsignedint answer=0;
unsignedint i;
/* 使用位移操作来控制次数,这样可以避免不可移植性
* i不是0就继续进行,这就使用循环与机器字长无
* 关,从而避免了可移植性问题
*/
for (i = 1; i != 0; i <<= 1) {
/*
*把旧的answer左移1位,为下一个位留下空间;
*如果value的最后一们是1,answer就与1进行or操作;
*然后将value右移一位
*/
answer <<= 1;
if (value & 1) {
answer |= 1;
}
value >>= 1;
}
return answer;
}
int main(int argc, char **argv) {
printf("%u",reverse_bits(25));//2550136832
}
[C和指针]第一部分的更多相关文章
- C++二级指针第一种内存模型(指针数组)
二级指针第一种内存模型(指针数组) 指针的输入特性:在主调函数里面分配内存,在被调用函数里面使用指针的输出特性:在被调用函数里面分配内存,主要是把运算结果甩出来 指针数组 在C语言和C++语言中,数组 ...
- C和指针 第一章 字符串处理程序
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_COL 20 #def ...
- 深入理解C指针第一章小结1
1.1 指针和内存,C程序在编译后,会以三种形式使用内存. (1) 静态/全局内存:在程序开始时分配,程序结束才消失,所有函数都能访问全局变量,static静态变量的作用域局限在定义它们的函数内部 ( ...
- C语言回顾-指针
1.指针:地址 指针变量:存放指针的变量 指针变量的定义:数据类型 *指针变量名 或者 数据类型* 指针变量名 指针变量的初始化:int *p=&a;int *p=NULL;(不能先定义后初始 ...
- 关于VS打开cshtml出现 未能完成该操作。无效指针
关于VS打开cshtml出现 未能完成该操作.无效指针 第一步:关闭VS 第二部:删除%LocalAppData%\Microsoft\VisualStudio\14.0\ComponentModel ...
- LoadRunner 脚本学习 -- 指针基础
先搞清楚 ++a 和 a++的区别 ++a : 前缀++, 先自增,后表达式 a++ : 后缀++, 先表达式,后自增 前缀,自增立即生效. 后缀,下次才会看到效果. 一维数组的指针 Action ...
- C语言 二级指针内存模型①
//二级指针第一种内存模型 #include<stdio.h> #include<stdlib.h> //说明:①:类似于int a[5]={0},数组名a是一维数组a中首元素 ...
- C 中typedef 函数指针的使用
类型定义的语法可以归结为一句话:只要在变量定义前面加上typedef,就成了类型定义.这儿的原本应该是变量的东西,就成为了类型. int integer; //整型变量int *pointer ...
- 从汇编看c++成员函数指针(三)
前面的从汇编看c++中成员函数指针(一)和从汇编看c++成员函数指针(二)讨论的要么是单一类,要么是普通的多重继承,没有讨论虚拟继承,下面就来看一看,当引入虚拟继承之后,成员函数指针会有什么变化. 下 ...
随机推荐
- 161107、spring异步调用,完美解决!
项目中,用户抢单,下单需要向对方推送消息,但是加上推送就会造成抢单和下单性能降低,反应变慢,因为抢单下单动作跟推送部分是同步的,现在想改成异步推送. 在Java应用中,绝大多数情况下都是通过同步的方式 ...
- Calendar的问题
1. include file is not work now. remove <!-- #include file="Calendar.js" -->, add &l ...
- 转:redis常用命令
一 Redis介绍 Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.从2010年3月15日起,Redis的开发 ...
- NSHashtable and NSMaptable
本文转自Nidom的博客,原文:<NSHashtable & NSMaptable> NSSet, NSDictionary, NSArray是Foundation框架关于集合 ...
- mongo 学习教程(全)
看的是爱酷学习网的视频:http://www.icoolxue.com/album/show/98 01 安装 1.先建mongoDB-data文件夹存数据 2.安装DB 3.设置环境变量:把bin目 ...
- java 面试每日一题6
题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 1.程序分析: 兔子的规律为数列1,1,2,3,5 ...
- React.js入门小案例
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title&g ...
- 关于hibernate对应关系之后取值的问题
hibernate对应关系之后取值,比如一对一关系,取不到值,需要检查PO类中是否生成了getter及setter方法.
- sqlite3常用命令&语法
sqlite数据库只用一个文件就ok,小巧方便,所以是一个非常不错的嵌入式数据库,SQLite大量的被用于手机,PDA,MP3播放器以及机顶盒设备. Mozilla Firefox使用SQLit ...
- 过滤器(Filter)案例:检测用户是否登陆的过滤器
*****检测用户是否登陆的过滤器:不需要用户跳转到每个页面都需要登陆,访问一群页面时,只在某个页面上登陆一次,就可以访问其他页面: 1.自定义抽象的 HttpFilter类, 实现自 Filter ...