C语言知识总结(二)

来源:互联网 发布:玲珑密保网络错误 编辑:程序博客网 时间:2024/06/08 04:38

64位,32位系统区别:

所谓64位操作系统者,是指能在64位CPU上运行的系统。

所谓64位CPU,这64位指的是数据字长,而不是地址字长。

指针最终归结到地址。因此,两者没有必然的联系。都是64位,是允许的;一个是,一个不是,也是允许的。
譬如,过去的8位CPU,多数是,数据长8位,地址长16位。最早的16位CPU,数据16位,地址20位。
一般的8位编译器,指针多是16位的。但是,如果你设计一个Z80系统,为了让它能访问汉字,采用地址复用技术,让它能访问1MB空间(20位地址)。


数据类型

7.数据类型(数据类型本质是固定内存大小的别名

数据类型和变量的关系:通过数据类型定义变量

Type Size in BytesMinimum Value Maximum Value

char 1                -128                                                127

short 2                -32,768                                          32,767

int              2                -32,768                                          32,767

long          4                -2,147,483,648                 2,147,483,647

unsigned char      1                      0                            255

unsigned short       2                      0                   65535

unsigned int             2                      0                   65535

unsigned long        4                      0                         4,294,967,295

float                      4                3.4E-38                                          3.4E+38

double                 8            1.7E-308                                       1.7E+308

long double        10                3.4E-4932                                     1.1E+4932

NOTE: Values given for one machine. Actual sizes are machine-dependent.

 

 

nt型字长问题:

① C/C++规定int字长和机器字长相同;

② 操作系统字长和机器字长未必一致;

③ 编译器根据操作系统字长来定义int字长;

  由上面三点可知,在一些没有操作系统的嵌入式计算机系统上,int的长度与处理器字长一致;有操作

系统时,操作系统的字长与处理器的字长不一定一致,此时编译器根据操作系统的字长来定义int字长:"

比如你在64位机器上运行DOS16系统,那么所有for dos16的C/C++编译器中int都是16位的;在64位机器上

运行win32系统,那么所有for win32的C/C++编译器中int都是32位的"

 

char

short

int

unsigned int

float

double

long

unsigned long

long long

*(即指针变量)

32位

1(固)

2(固)

4(固)

4(固定)

4(固)

8(固)

4

4 (寻址控件的地址长度数值)

8(固定)

4(32位寻址空间是4个字节)(变)

64位

1(固)

2(固)

4(固)

4(固定)

4(固)

8(固)

8

8

8 (固)

8(64位编译器)

除了*与long随操作系统子长变化而变化外,其他的都固定不变(32位和64相比)

bool 1个字节  char 1个字节     int 4个字节   float 4个字节       doubl 8个字节  long long 8个字节

 

void类型的变量不占空间,理论上应该sizeof(void)是0,在VC中的确也是0,而gcc把它定为1。
如果是C++的话, sizeof(void)根本是编译通不过的。
C++中一个空的结构或类:
struct EMPTY{};    sizeof( struct EMPTY); 也是返回1。(C++中的类也是如此)
但如果是C语言,gcc中就会返回0。而VC中根本不允许空的结构。

 

Char总是占1个字节,其他数据类型为多少字节取决于机器

1int就是32位的一个二进制整数,在内存当中占据4个字节的空间(不管32位,还是64位下,不论是windows还是unix都是4个字节的)

2short意思为短整数,在32位系统下是2个字节,16个比特

3long意思为长整数,在32位的系统下,long都是4个字节的,在64位系统下,windows还是4个字节,unix下成了8个字节。

4Long long是64位,也就是8个字节大小的整数,对于32位操作系统,CPU寄存器是32位,所以计算longlong类型的数据,效率很低

4char c;定义一个char变量  ‘a’,char的常量 Char的本质就是一个整数(对应ASCII码),一个只有1个字节大小的整数.字符常量四个字节,字符变量一个字节

char取值范围为-128到127      unsignedchar为0-255

链接:转义字符:\a,警报 \b退格 \n换行  \r回车  \t制表符  \\斜杠  \’单引号  \”双引号  \?问号

5浮点类型(float,double,longdouble类型):[浮点数据并非绝对精确,都是近似值]

Float在32位系统下是4个字节,double在32位系统下是8个字节

小数的效率很低,避免使用,除非明确的要计算一个小数。

链接:浮点数的存储:

1.浮点型比较:不应比较两浮点值是否完全相等,而应比较他们是否接近相等,接近相等的浮点值视为相等。

所以不应该是num1==num2,而是fabs(num1-num2)<0.00001

 

浮点型直接常量前:

    1) 加前缀F 或 f是float型, 例如94.6F

    2) 加前缀L 或l 是long double型, 例如94.6L

自动类型转换:赋值时可能会发生自动类型转换。

强制类型转换:

9l,9L,9ll,9LL,9u,9ull,9ULL

 

(3)整数溢出

计算一个整数的时候超过整数能够容纳的最大单位后,整数会溢出,溢出的结果是高位舍弃。

当一个小的整数赋值给大的整数,符号位不会丢失,会继承

 

数据类型的本质

从编译器和内存的角度理解数据类型:

Int a 告诉C编译器给我分配四个字节的内存。(程序通过变量来申请和命名内存空间,通过变量名访问内存空间(一段连续)内存空间的别名(是一个门牌号),)

1、数据类型的本质:可理解为创建变量的模具,是固定内存大小的别名。

2、数据类型的作用:编译器预算对象(变量)分配的内存空间大小

3、求数据类型大小:sizeof()

4、数据类型可以有别名   typedef uu int;  sizeof(uu)=sizeof(int);


常量

1)常量 define和const:常量就是在程序中不可变化的量,常量在定义的时候必须给一个初值

const int  a;//常量的定义只能在赋值的时候(报错)

const int a;

int const b; //第一个第二个意思一样 代表一个常整形数

const int *c; //第三个 c是一个指向常整形数的指针(所指向的内存数据不能被修改,但是本身可以修改)

int * const d; //第四个 d 常指针(指针变量不能被修改,但是它所指向内存空间可以被修改)

const int * const e ; //第五个 e一个指向常整形的常指针(指针和它所指向的内存空间,均不能被修改)

Int func1(const )

//指针变量和它所指向的内存空间变量,是两个不同的概念。。。。。。

//const 是放在*的左边还是右边const是修饰指针变量,还是修饰所指向的内存空变量

Const好处

//合理的利用const,

//1指针做函数参数,可以有效的提高代码可读性,减少bug;

//2清楚的分清参数的输入和输出特性

int setTeacher_err( const Teacher *p)

Const修改形参的时候,在利用形参不能修改指针所向的内存空间

C中“冒牌货”(C语言可以强转实现改掉所谓的常量)

const int a = 10;

int *p = (int*)&a; //c语言的强制转换,无法改变值 (c语言可以,c++不行

         printf("a===>%d\n", a);

         *p = 11;

         printf("a===>%d\n", a);

解释:C++编译器对const常量的处理

当碰见常量声明时,在符号表中放入常量 =è问题:那有如何解释取地址

编译过程中若发现使用常量则直接以符号表中的值替换

编译过程中若发现对const使用了extern或者&操作符,则给对应的常量分配存储空间(兼容C)

?联想: int &a = 1(err) & const int &a = 10(ok)?引用常量和常量引用

Const int a=1与const int&a=1是等效的在这里

voidfun1(constint&a)//这里可以输入数字

{

         cout <<a<<endl;

}

voidfun2(int&a)//这里可以输入数字

{

         cout <<a<<endl;

}

注意:C++编译器虽然可能为const常量分配空间,但不会使用其存储空间中的值。


const和#define相同之处

C++中的const修饰的,是一个真正的常量,而不是C中变量(只读)。在const修饰的常量编译期间,就已经确定下来了。

对比加深

C++中的const常量类似于宏定义

const int c = 5; ≈ #define c 5

C++中的const常量与宏定义不同

const常量是由编译器处理的,提供类型检查和作用域检查

宏定义由预处理器处理,单纯的文本替换

在某个函数体内,#define,可以被其他函数体使用

C语言和c++中的const对比结论

C语言中的const变量

C语言中const变量是只读变量,有自己的存储空间

C++中的const常量

可能分配存储空间,也可能不分配存储空间 

const常量为全局,并且需要在其它文件中使用,会分配存储空间

当使用&操作符,取const常量的地址时,会分配存储空间

const int &a = 10; const修饰引用时,也会分配存储空间

注意:对于#define类型的常量,c语言的习惯是常量名称为大写,但对于普通const常量以及变量,一般为小写结合大写的方式

 

链接:define和typedef区别:

typedef是一种高级数据特性,它能使某一类型创建自己的名字

typedef unsigned char BYTE

1与#define不同,typedef仅限于数据类型,而不是能是表达式或具体的值

2typedef是编译器处理的,而不是预编译指令

3typedef比#define更灵活

直接看typedef好像没什么用处,使用BYTE定义一个unsigned char。使用typedef可以增加程序的可移植性

typedef定义函数指针

typedef const char *(*SUBSTR)(const char *, const char *);

const char *getsubstr(const char *src, const char *str)

{

         return strstr(src, str);

}

const char *func(const char *(*s)(const char *, const char *), const char *src, const char *str)

const char *(*p[3])(const char *, const char *);

 

(2)变量:(类型)内存中的一块空间(里面的值是可变的)

定义形式:类型  标识符, 标识符, … , 标识符 ;

变量(标识符)命名规则:

标示符:只能由字母、数字、下划线组成,第一个字母必须是字母或下划线,大小写有区别,不能使用C语言的关键字。如果变量不初始化,就会默认读取垃圾数据, 有些垃圾数据会导致程序崩溃。

变量的本质一段连续内存空间的别名

1、  程序通过变量来申请和命名内存空间 int a = 0;(变量名a在代码区,是0这个四字节内存空间的别名)

2、  通过变量访问内存空间

修改变量的三只方法:

1、直接:int a = 10;   a=20;

2、间接。内存有地址编号,拿到地址编号也可以修改内存;

&a=1245024;

*((int *)(1245024)) = 10;

3、空间可以再取给别名吗?C++中的引用

 

声明变量的意义

C语言为什么要规定先声明变量呢?为什么要指定变量的名字和对应的数据类型呢?

(1)建立变量符号表。

通过声明变量,编译器可以建立变量符号表,如此一来,程序中用到了多少变量,每个变量的类型是什么,编译器非常清楚,是否使用了没有声明的变量,编译器在编译期间就可以发现。从而帮助了程序员远离由于疏忽而将变量名写错的情况。

(2)变量的数据类型指示系统分配多少内存空间。

(3)变量的数据类型指示了系统如何解释存储空间中的值。

同样的数值,不同的类型将有不同的解释。int占据4个字节,float也占据4个字节,在内存中同样也是存储的二进制数,并且这个二进制数也没有标志区分当前是int型还是float型。如何区分?就是通过变量的数据类型来区分。由于声明建立了变量符号表,所以系统知道变量该如何解释。

(4)变量的数据类型确定了该变量的取值范围

例如短整型数据取值-32767~32767之间。

(5)不同的数据类型有不同的操作

如整数可以求余。C语言用符号”%”表示求余。整数可以,实数不可

变量三要素(名称、大小、作用域),变量的生命周期   【内存四区模型

 

8.类型限定:

(1)volatile代表变量是一个可能被CPU指令之外的地方改变的,编译器就不会针对这个变量去优化目标代码。

如果不加入volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。

volatile的作用是: 指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值

这是区分C程序员和嵌入式系统程序员的最基本的问题:嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所有这些都要求使用volatile变量。不懂得volatile内容将会带来灾难。

⒈  编译器的优化

在本次线程内,当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后再取变量值时,就直接从寄存器中取值;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致。

当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致

当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致

举一个不太准确的例子:

⒉ 在什么情况下会出现

1). 并行设备的硬件寄存器

2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

3). 多线程应用中被几个任务共享的变量

“易变”是因为外在因素引起的,像多线程,中断等,并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化;

当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

一般说来,volatile用在如下的几个地方:

1、中断服务程序中修改的供其它程序检测的变量需要加volatile;

2、多任务环境下各任务间共享的标志应该加volatile

3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义;

另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。

假设编译器发现Sleep(1000)是调用一个外部的库函数,它不会改变成员变量flag_,那么编译器就可以断定它可以把flag_缓存在寄存器中,以后可以访问该寄存器来代替访问较慢的主板上的内存。这对于单线程代码来说是一个很好的优化,但是在现在这种情况下,它破坏了程序的正确性

在大多数情况下,把变量缓存在寄存器中是一个非常有价值的优化方法,如果不用的话很可惜。C和C++给你提供了显式禁用这种缓存优化的机会。

如果声明变量是使用了volatile修饰符,编译器就不会把这个变量缓存在寄存器里——每次访问都将去存取变量在内存中的实际位置。

在Java中设置变量值的操作,除了long和double类型的变量外都是原子操作,也就是说,对于变量值的简单读写操作没有必要进行同步。

在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。

要解决这个问题,只需要像在本程序中的这样,把该变量声明为volatile(不稳定的)即可,这就指示JJVM,这个变量是不稳定的,每次使用它都到主存中进行读取。一般说来,多任务环境下各任务间共享的标志都应该加volatile修饰。

Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。

这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。

而volatile关键字就是提示JVM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。

使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。

由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字

(2)register(见c++部分)

变量在CPU寄存器里面,而不是在内存里面。但regist是建议型的指令,而不是命令型的指令

3const    见上文   

 

Sizeof

注意:sizeof()是操作符(单目运算符),不是函数。

sizeof操作符以字节形式给出了其操作数的存储大小。

操作数的存储大小由操作数的类型决定。

Sizeof测量的实体大小在编译期间就已经确定

 注意:sizeof操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型

 

sizeof操作符的结果类型size_t,它在头文件<stddef.h>中typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。 
  1、若操作数具有类型char、unsigned char或signed char,其结果等于1。  ANSI C正式规定字符类型为1字节。 
  2、int、unsigned int 、short int、unsigned short 、long int 、unsigned long 、float、double、long double类型的sizeof 在ANSI C中没有具体规定,大小依赖于实现,一般可能分别为2、2、2、2、4、4、4、8、10。 
  3、当操作数是指针时,sizeof依赖于编译器。一般Unix的指针字节数为4。 
  4、当操作数具有数组类型时,其结果是数组的总字节数。 
  5、联合类型操作数的sizeof是其最大字节成员的字节数。结构类型操作数的sizeof是这种类型对象的总字节数(内存对齐),包括任何垫补在内。 
  让我们看如下结构: 
  struct {char b; double x;} a; 在某些机器上sizeof(a)=12,而一般sizeof(char)+ sizeof(double)=9。 
  这是因为编译器在考虑对齐问题时,在结构中插入空位以控制各成员对象的地址对齐。如double类型的结构成员x要放在被4整除的地址。 
  6、如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小