C专家编程(Expert C Programming)(三)
1、常引用
const int &a=1;//right
int &b=1; //not right
char s[]="hello";
char* &str=s;//不可以,涉及到常量/常指针的类型,都不能定义如此引用操作
2、数组的初始化
int n=10;
int a[n];
在不同的编译器上结果不一样。GCC可以通过,而VS通不过。这主要是由于不同编译器处理变量初始化的时间不一样导致的。
我们观察如下一下现象:
const int c=10;
int main()
{
int a[c];
}
是可以在VS中通过的。也就是说,在定义const变量的同时,就对其初始化,而这个工作在编译阶段就完成了,所以数组a的值是确定的。
static int c=10;
int main()
{
int a[c];
}
是不能通过的,因为,static变量是在第一次访问他的时候才初始化,而我们第一次访问他是在运行阶段。
函数内部的静态变量是在第一次访问的时候来初始化。在C++中,操作是否合法是在编译时检查的。
C++中理解“初始化不是赋值”是必要的。初始化指创建变量并给它赋初始值,而赋值则是擦除对象的当前值并用新值代替。
内置类型变量是否自动初始化取决于变量定义的位置。在函数体外定义的变量都初始化成0,在函数体里定义的内置类型变量不进行自动初始化。除了用作赋值操作符的左操作数,未初始化变量用作任何其他用途都是没有定义的。
编译预处理阶段进行宏的替换等操作,编译阶段进行词法,语法,分析,目标代码的生成和优先,变量内存的分配等,链接阶段则将目标文件和相关的库文件和函数进行链接。
在main函数外的那些全局变量和静态变量,编译时就在exe文件中占了一个位置,相对于exe文件的起始地址已经是固定的了。程序加载后这些变量的相对位置已是固定的了,而函数内的运行时会在栈上重新开辟空间。但是,普通的全局变量也只是分配可重定位的地址(或占了一个位置),并没有初始化赋值,而是到开始运行阶段时才赋值。
在VS环境下,访问未初始化的变量导致运行错误,而GCC则默认将其设为0,而不管是在函数内部,还是外部。
extern double pi = 3.1416; // definition
虽然使用了extern,但是这条语句还是定义了pi,分配并初始化了存储空间。只有当extern声明位于函数外部时,才可以含有初始化式。
3、lint程序(linux下)
4、用getche();和getch()来获取单个字符。
5、类型提升
#include "cstdio"
#include "cstdlib"
#include "iostream"
using namespace std;
int main(){
union{
double d;
float f;
}u;
u.d=10;
printf("f= %f \n",u.f);
u.f=10;
printf("f= %f \n",u.d);
}
int main(){
union u{
int a;
char s[4];
u b;
b.s[0]=0x23;
b.s[1]=0x34;
b.s[2]=0x35;
b.s[3]=0x36;
cout<<hex<<b.a;
我们可以看到,整数的输出是先提取高字节,后低字节,也就是所谓的高字节存储高位。
我们还发现,其共享是从共享空间的低地址开始为偏移的。如果涉及到double,float数据,则由于在其内部,按照IEEE的操作方法,有尾数,基数,指数等存储格式,已不能按常理分析,就套格式。
6、数组的声明就是数组,指针的声明就是指针,定义和声明必须匹配。对编译器而言,一个数组就是一个地址,而一个指针就是一个地址的地址。
作为函数定义的形式参数,char s[]与char *s是一样的。
(1)表达式中的数组名,(与声明不同)被编译器当作一个指向该数组第一个元素的指针。
(2)下标总是与指针的偏移量相同。
(3)只有在函数参数的声明中,数组名始终被编译器修改成指向该数组第一个元素的指针。
数组下标是定义在指针的基础上,所以优化器常将它转换成有效率的指针表达形式。C语言把数组下标改写成指针偏移量的原因是指针和偏移量是底层硬件所使用的基本模型。
所以
fun(int *tun);
fun(int tun[]);
fun(int tun[20]);
是等价的。
因为“在函数参数的声明中,数组名被编译当作指向该数组第一个元素的指针”,所以
void doit(int p[])
{
int *p2=NULL;
p=p2;//right,处理成数组
int a[3];
a=p2;//error
}
只有字符串常量才可以初始化指针数组。多维数组中,最右边的下标变化最快。
7、int a[2][3][4]
a的类型是int[2][3][4],而a指向元素的类型是int[3][4]
a[2]的类型是int[3][4],而a[2]指向元素的类型是int[4]
a[2][3]的类型是int[4],a[2][3]指向元素的类型是int。
8、等价式
int s[i][j]的声明可以是如下一种:
int s[23][12]
int *s[23]
int **s;
int (*s)[12]
局部引用的规则:一次读写的数据位于同一页面上。
9、交换两个数不通过第三个数
a=a+b;
b=a-b;
a=a-a;
或通过异或。
b=a^b^a
如果b=a,则结果为0,否则可以交换。不过,还有一些限制条件:如果一个变量存储于寄存器或者是一个位段,则因取不到寄存器或位段的地址而失败,如果a,b长度不同,或者其中之一指向一个数组,也会失败。
10、在C语言中,没有办法向函数传递一个普通的多维数组,因为我们需要知道每一维的升序,以便为地址运算提供正确的单位长度。
fun(int a[10][20]);
fun(int a[][20]);
fun(char **p);//只有二维数组改为一个指向向量的指针数组的前提下才可以这么做。
对于一维数组,在函数传参过程中,可以用前面的方法;二维字符串数组,可以指针数组,其它类型,还要加一个越界位置的结束标志。
三维则需要分解之。
11、可以让函数返回一个指向任何数据结构的指针。
#include "iostream"
using namespace std;
typedef int (*AP)[20];
AP p2=NULL;
int a[2][20];
int a1[20];
int a2[20];
AP TestDo(){
p2=a;
p2[0][1]=1;
p2[1][2]=1;
return p2;
}
int main(){
TestDo();
cout<<a[0][1]<<" "<<a[1][2]<<endl;
}
注意:不能从函数中返回一个指向函数的局部变量的指针。
12、实现动态数组
int main()
{
int size;
char *dynamic;
char input[10];
cout<<"input size:"<<endl;
size=atoi(fgets(input,7,stdin));
dynamic=(char*)malloc(size);
dynamic[0]='a';
dynamic[1]='\0';
cout<<dynamic;
}
动态增长的另一种方法是使用链表,但是不能随即访问。
13、在C++中,内层变量屏蔽外层。在C++中字符常量的类型是char,而C中它们的类型是int.