C重新学习C语言(十年前的连载)

重新学习 c 语言(3)- c语言特性(二)数据对象

2019-06-16  本文已影响0人  hotplum

(二) 数据对象

所以我们先来说说这个数据对象,数据对象大概包含一下特征.

(1) 属性

数据对象包含哪些属性?

还有吗?.

我这里不按照数据对象是常量,变量还是数据类型(typedef 关键词定义 pascal 中用 type定义,数据类型暂不考虑,她只有名称和类型,像是类型的代号),我也搞不清常量和变量的确切定义(依据编译后机器语言的处理形式?比如说常量不占用内存,比方立即数,立即数是编码在代码中的,浮点数又有区别,立即数故名思意,当时有效,过后无效了,也就是没有生存期,作用域也是一个点),其实似乎搞清楚也没有什么太大用处.通常我们都是通过对象(数据对象简称对象吧)名称访问对象的值,但是立即数没有名称,还有一种比如通过手动分配内存(如malloc 函数)的变量也没有名称,需要通过间接引用访问其值.

对象的值是根据对象类型做出的解释,当然一般值都有内存占用,内存占用的空间就是其类型的长度(尺寸),当然实际尺寸和宿主有关,我们不用考虑,姑且认为char占用一个字节(8bit).不同长度(尺寸)的对象类型肯定不同,相同长度(尺寸)的对象类型也可能不同,比如 long(认为是4个字节) 和float.

C语言基本数据类型非常简单(其实,计算机的数据原本就简单,都是二进制表示的数),依据计算机处理的不同就两种类型 整型和浮点型!

整型根据长度和有无符号表示的不同分为一下几种类型

类型 长度 备注
unsigned char 8位 无符号char
unsigned short int 一般位16位 无符号 int
unsigned long int 一般为32位 无符号 long
unsigned int 根据计算机字长,32位机是32位 无符号 int
signed char 8位 符号char
signed short int 一般位16位 符号 int
signed long int 一般为32位 符号 long
signed int 根据计算机字长,32位机是32位 符号 int

long 和short 修饰int时 int可以省略

另外c99支持long long 用来表示64位整型.(其实在64位机器上int就是64位,c设计者为了编译器的效率没有规定各种类型必须的长度,只是说short不长于int,int不长于long,所以有可能short=int=long,由编译器决定长度对程序性能来讲是有很大优势的)

无符号整型比较简单就是2的n次幂 -1 最大可以表示2的n次幂个数(n是位长)
有符号整型采用二进制补码格式表示!

浮点类型 采用IEEE格式

根据长度分为 float (32位) double (64位) 还有IA32CPU体系上的long double (80位)
由基本数据类型可以组成复合数据类型,比如

指针的设计是c语言的灵魂,但也是把双刃剑!除了指针存放数据对象的地址这一特性,还有就是编译器提供的指针运算为指针的使用提供了广泛的用途!

先看一个例子:
比如++运算对于指针来讲是什么意思呢? 对于一个整型

int a = 10; 
int b=a++;   //b =11

a++表达式求值结果还是一个整型的数据对象.
上面的pSize 一样
int *pNext=pSize++;
pSize++表达式求值结果还是一个整型指针(地址),但这个地址是多少呢?
c语言是这样定的:pNext的地址是pSize地址+int数据类型的长度(比如4个字节,记住,字节是cpu处理地址的原子单位)

pointer.jpg

对于数组

char str[]=”hello,world”;

str是一个char数组长度初始化为”hello,world”的长度12,字符11一个’\ 0’.

char *pStr = &str[0];

利用指针运算,比如 *(pStr+4) 和 str[4] 是一样的!

pStr+4意味着第0个字符地址+4个字节后的地址,这个地址上的值(类型是char已经说明).

简单总结一下指针的运算

  1. 指针与相同类型指针的赋值和比较(一般指针在同一个数组中比较有意义).

  2. 将指针赋值为0,以及指针与0 的比较运算(将指针赋值为空,以及判断指针是否为空)

  3. 指针与整数之间的加减运算(相当于地址向后或向前移动sizeof(指针类型)字节).

  4. 两个指针的减法(一般在同一个数组中有意义)

打住….

(2) 作用域

其实应该先说生存期.不过这两个概念在任何语言中都是有的.作用域其实是一个编译时期的概念. 作用域与数据对象的声明有关系:
先说说代码块,作用域的空间(生存期是时间特性,哈哈难以避免的时空).
c语言的代码块(为了简单不考虑#include情况了,其实#include是在预编译时将引用的代码展开在文件处,所以按源文件考虑是一样的.一般c语言的声明在.h文件中,后来的语言为了避免引用的命名冲突,使用了namespace).

一个源文件是最大的代码块(其实整个程序是最大的代码块),包含在里面的函数是一层代码块,函数内部包含

{ }的语句也是代码块. 小的代码块总是被大的代码块包含.代码块中包含的代码块我们暂且成为子代码块.

C语言(后来的标准)允许在任何位置声明变量(数据对象).

理解了代码块就理解作用域了.

作用域范围就是从声明开始之后的代码块中,包含子代码块.这里说的是声明,不是定义.

函数的局部变量定义就是声明.声明外部变量用关键字extern

比如:

void print(void)
int main(int argc,char *argv[])
{
  if (argc>2)
  {
       extern a;
       printf("%d\n",a);
  }
  //int b = a; //这里不属于a的作用域
  print();
  return 0;
}
int a =100;  //定义在这里 这以后默认声明了 ,所以下面的子代码块中都是可见的
void print(void)
{
  printf("%d\n",a);
}

如果是定义在另外一个文件中的外部变量(哈哈,函数都是外部的),我们能不能可见呢?
先看看生存期.

(3) 生存期

c语言数据对象的生存期 分为三种:

外部变量: 生存期由编译器和操作系统决定,一般来说生存期从定义开始到程序结束为止.

内部变量(c语言也叫auto,另外寄存器变量就不考虑了),一般在函数内部(函数的子代码块中的定义的变量也一样)从定义开始(其实是函数开始),到函数结束为止.

动态创建的数据对象只是那些比如通过malloc调用得到的内存块上的数据对象,生存期靠程序员决定(不错,给程序员很大的发挥空间,不过是把”双刃剑”,以至于java不允许干这个事情).

另外注意一个关键字 static

如果是外部变量static修饰限制了这个外部变量的作用域,只能在本文件中使用.

如果是内部变量static ,改变了这个变量的生存期(类似一个外部变量).

当然函数也是外部的,如果用 static修饰,也是改变了这个函数的作用域,只能在本文件中使用.

没有static修饰的外部变量其实作用域可以是整个程序.

另外c引入c++的const关键字只是说明该变量的只读特性.仅此而已.

复杂类型的定义与声明

数组 数组定义时必须指明长度: int arrCount[10];

  如果定义时之间初始化,则可以不指明长度,使用初始化常量中的长度.

     Int arrCount[]={1,2};

声明时不需要指定长度,如果指定的长度和定义的不符则运行出错(一般编译器不检查此类错误) extern b[];

记录

首先定义一个记录类型(是一种数据类型)

struct String
{
  int len;
  char *str;
};
//根据类型定义记录数据对象
   struct String str;
// 或者同时定义数据类型和数据对象

struct String
{
   int len;
  char *str;
}str;

作为外部变量声明时 需要指明数据类型

extern String str;

另外数组在参数传递和返回时的表现和记录不同!在下面的函数和程序结构探讨; 先说点别的.
哈哈,似乎我漏掉了最常用的一种数据类型: 字符串.

在c语言中字符串不是语言的内建类型,或者说c语言没有对字符串内建支持(pascal/Delphi等对字符串提供了内建支持),对于编译语言,语言的内建支持其实就是编译器的支持(Delphi的AnsiString的实现非常巧妙,既有效率,使用又简单,但缺点也是明显的其中之一就是如果不了解编译器的实现原理,一旦出现问题,会让程序员不知所措,或许也是c语言没有内置字符串支持的原因).

先说说字符,字符就是整型,根据不同的编码标准得到不同的字符集比如单字节的ASCII编码(其实是7位编码),双字节的unicode编码,还有许多编码标准,目前在网络应用比较广泛的是utf-8编码(不固定字节长度,但在0-127之间的编码兼容ASCII).对于ASCII编码,c语言是内置支持的,比如 char a = ‘a’; 变量a中存放的其实是97.字符编码就是某种字符由哪个整型数表示.

字符串在c中就是字符数组.我们常说的以0 结尾的字符数组.而真正赋予这种”数据类型”字面含义的是操作字符串的库函数(在string.h中定义),所以使用c语言开发的同学会抱怨,简单的字符串相加都要调用函数实现.

如果这样岂不更好(c里面没有string 可以typedef……)

string a = “hello,”;

string b = “world”;

string c= a +b; // c为”hello,world”

很多其他语言就是这样的!比如c++ (c++使用运算符重载这种”高深”的特性,除了浪费开发者的脑力,不知道还有什么好处).

其实作为一个复合类型,赋值运算,相加运算都有他特定的含义.在c中典型的

char str[]=”hello,world”;

str其实是这样的,字符数组赋初值是编译器实现的(打住,如果有空一定要修炼计算机原理方面的知识…)

h e l l o , w o r l d \0

当然 string c = a + b 这种语法也需要编译器去实现.c语言其实可以自己编写string的实现(没有编译器支持,只能是靠函数了…)
下面可以看看delphi编译器(Delphi 7)实现的ansistring, 看上去大概是这样的:

struct String
{
  long refcount;  //在-8偏移处存放引用计数
  long length;    //在-4偏移处存放字符串长度
  char *str;       //与0结尾的字符串兼容
};

这样几乎解决了0结尾字符串的所有缺点.

引用计数和编译器的Copy On Write 使字符串一方面可以动态创建(随意变换长度),又减少了赋值次数.-4偏移的字符串长度可以方便的得到串长(因为许多字符串操作依赖字符串长度,0结尾的字符串需要遍历字符串得到长度),另外0结尾的字符串不能存放字符’\0’,ansiString就可以.最后字符串实际内容存放着0结尾的字符串,兼容了c的这种字符串形式. 不过AnsiString需要编译器做许多工作实现生存期自管理和Copy On Write 等功能,高级的字符串操作需要我们了解编译器的行为.

如果喜欢,我们也可以用c实现字节的字符串格式和相应的操作函数.

语言的数据对象是核心内容,但根本上依赖计算机原理,所以随着对计算机系统的深入,了解的也越透彻,看问题反而越简单.深奥的原理其实往往是简单并且美的.

上一篇下一篇

猜你喜欢

热点阅读