博文

C语言初学者入门讲座 第二讲 数据类型(2)(2007-05-03 10:51:00)

摘要:C语言初学者入门讲座 第二讲 数据类型(2)   字符型量

  字符型量包括字符常量和字符变量。

  字符常量

  字符常量是用单引号括起来的一个字符。例如'a','b','=','+','?'都是合法字符常量。在C语言中,字符常量有以下特点:

  1.字符常量只能用单引号括起来,不能用双引号或其它括号。

  2.字符常量只能是单个字符,不能是字符串。

  3.字符可以是字符集中任意字符。但数字被定义为字符型之后就不能参与数值运算。如'5'和5 是不同的。'5'是字符常量,不能参与运算。

  转义字符

  转义字符是一种特殊的字符常量。转义字符以反斜线"/"开头,后跟一个或几个字符。转义字符具有特定的含义,不同于字符原有的意义,故称“转义”字符。例如,在前面各例题printf函数的格式串中用到的“/n”就是一个转义字符,其意义是“回车换行”。转义字符主要用来表示那些用一般字符不便于表示的控制代码。

  常用的转义字符及其含义

转义字符 转义字符的意义
/n   回车换行
/t 横向跳到下一制表位置
/v  竖向跳格
/b 退格
/r  回车
/f  走纸换页
// 反斜线符"/"
/'  单引号符
/a  鸣铃
/ddd  1~3位八进制数所代表的字符
/xhh 1~2位十六进制数所代表的字符
 
  广义地讲,C语言字符集中的任何一个字符均可用转义字符来表示。表2.2中的/ddd和/xhh正是为此而提出的。ddd和hh分别为八进制和十六进制的ASCII代码。如/101表示字?quot;A" ,/102表示字母"B",/134表示反斜线,/XOA表示换行等。转义字符的使用。

void main()
{
 int a,b,c;
 a=5; b=6; c=7;
 printf("%d/n/t%d %d/n %d %d/t/b%d/n",a,b,c,a,b,c);
}

  ......

阅读全文(1444) | 评论:0

C语言初学者入门讲座 第二讲 数据类型(1)(2007-05-03 10:50:00)

摘要:C语言初学者入门讲座 第二讲 数据类型(1)   我们已经看到程序中使用的各种变量都应预先加以说明,即先说明,后使用。对变量的说明可以包括三个方面:

  ·数据类型
  ·存储类型
  ·作用域

  在本讲中,我们只介绍数据类型说明。其它说明在以后陆续介绍。所谓数据类型是按被说明量的性质,表示形式,占据存储空间的多少,构造特点来划分的。在C语言中,数据类型可分为:基本数据类型,构造数据类型,指针类型,空类型四大类。

  1.基本数据类型

  基本数据类型最主要的特点是,其值不可以再分解为其它类型。也就是说,基本数据类型是自我说明的。

  2.构造数据类型构造数据类型

  是根据已定义的一个或多个数据类型用构造的方法来定义的。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或又是一个构造类型。在C语言中,构造类型有以下几种:

  ·数组类型
  ·结构类型
  ·联合类型

  3.指针类型

  指针是一种特殊的,同时又是具有重要作用的数据类型。其值用来表示某个量在内存储器中的地址。虽然指针变量的取值类似于整型量,但这是两个类型完全不同的量,因此不能混为一谈。4.空类型在调用函数值时,通常应向调用者返回一个函数值。这个返回的函数值是具有一定的数据类型的,应在函数定义及函数说明中给以说明,例如在例题中给出的max函数定义中,函数头为: int max(int a,int b);其中“int ”类型说明符即表示该函数的返回值为整型量。又如在例题中,使用了库函数 sin,由于系统规定其函数返回值为双精度浮点型,因此在赋值语句s=sin (x);中,s 也必须是双精度浮点型,以便与sin函数的返回值一致。所以在说明部分,把s说明为双精度浮点型。但是,也有一类函数,调用后并不需要向调用者返回函数值, 这种函数可以定义为“空类型”。其类型说明符为void。在本讲中,我们先介绍基本数据类型中的整型、浮点型和字符型。其余类型在以后各讲中陆续介绍。

  对于基本数据类型量,按其取值是否可改变又分为常量和变量两种。在程序执行过程中,其值不发生改变的量称为常量,取值可变的量称为变......

阅读全文(1440) | 评论:0

C/C++编程新手错误语录(2007-04-21 17:41:00)

摘要: 1.引言

  还记得当年学数学、英语都有个窍门,那就是搞个错题集。经常复习一下这个错题集,就可以避免下次犯同样的错误。而几乎所有的程序员都是从犯错误开始的,我们也很有必要总结一下编程新手的常见错误,本文的目的在于此。文中所列出的都是笔者在项目开发中接触到的新手真实的言谈,笔者学学文革腔调,姑且称之为“错误语录”。

  2.语录   (1)“我的程序都是对的,可结果不对”

  想想你的周围,是不是也有人说这样的话?如果你也曾经说过,那就此打住,不要再说这句话,因为这句话只会显示说话者的无知。既然程序都是对的,那为什么结果不对?

  (2)“程序=算法+数据结构”

  如果刚刚学完C语言,我们说这样的话,完全可以理解,而且可以说是正确的。但是如果你是一位即将从事C/C++编程的程序员,那么很遗憾,这个说法只能判错,殊不知,世界上还有另一种说法:

  程序 = 对象 + 消息

  “程序=算法+数据结构”只对面向过程的语言(C)成立,而对面向对象的语言(C++),则只能表述为“程序=对象+消息”。传统的过程式编程语言以过程为中心以算法为驱动,面向对象的编程语言则以对象为中心以消息为驱动。这里的消息是广义的,对象A调用了对象B的成员函数,可看作对象A给B发消息。

  (3)“程序编出来,运行正确就行了”

  运行正确的程序并不一定是好程序,程序员时刻要牢记的一条就是自己写的程序不仅是给自己看的,要让别人也能轻易地看懂。很遗憾,许多的编程新手不能清晰地驾驭软件的结构,对头文件和实现文件的概念含糊不清,写出来的程序可读性很差。

  C程序采用模块化的编程思想,需合理地将一个很大的软件划分为一系列功能独立的部分合作完成系统的需求,在模块的划分上主要依据功能。模块由头文件和实现文件组成,对头文件和实现文件的正确使用方法是:

  规则1 头文件(.h)中是对于该模块接口的声明,接口包括该模块提供给其它模块调用的外部函数及外部全局变量,对这些变量和函数都需在.h中文件中冠以extern关键字声明;

  规则2 模块内的函数和全局变量需在.c文件开头冠以static关键字声明;

  规则3 永远不要在.......

阅读全文(1597) | 评论:0

new和delete导致的内存分配问题详解(2007-04-21 17:37:00)

摘要:在嵌入式系统中使用C++的一个常见问题是内存分配,即对new 和 delete 操作符的失控。

  具有讽刺意味的是,问题的根源却是C++对内存的管理非常的容易而且安全。具体地说,当一个对象被消除时,它的析构函数能够安全的释放所分配的内存。这当然是个好事情,但是这种使用的简单性使得程序员们过度使用new 和 delete,而不注意在嵌入式C++环境中的因果关系。并且,在嵌入式系统中,由于内存的限制,频繁的动态分配不定大小的内存会引起很大的问题以及堆破碎的风险。

  作为忠告,保守的使用内存分配是嵌入式环境中的第一原则。

  但当你必须要使用new 和delete时,你不得不控制C++中的内存分配。你需要用一个全局的new 和delete来代替系统的内存分配符,并且一个类一个类的重载new 和delete。

  一个防止堆破碎的通用方法是从不同固定大小的内存持中分配不同类型的对象。对每个类重载new 和delete就提供了这样的控制。

  重载全局的new 和delete 操作符

  可以很容易地重载new 和 delete 操作符,如下所示:

void * operator new(size_t size)
{
 void *p = malloc(size);
 return (p);
}
void operator delete(void *p);
{
 free(p);
}
  这段代码可以代替默认的操作符来满足内存分配的请求。出于解释C++的目的,我们也可以直接调用malloc() 和free()。

  也可以对单个类的new 和 delete 操作符重载。这是你能灵活的控制对象的内存分配。

class TestClass {
 public:
  void * operator new(size_t size);
  void operator delete(void *p);
  // .. other members here ...
};

void *T......

阅读全文(1753) | 评论:0

明晰C++内存分配的五种方法的区别(2007-04-21 17:36:00)

摘要: 作者:未知    文章来源:网络   在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。

  栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。

  堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

  自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。

  全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。

  常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多,在《const的思考》一文中,我给出了6种方法)

  明确区分堆与栈

  在bbs上,堆与栈的区分问题,似乎是一个永恒的话题,由此可见,初学者对此往往是混淆不清的,所以我决定拿他第一个开刀。

  首先,我们举一个例子:

void f() { int* p=new int[5]; }

  这条短短的一句话就包含了堆与栈,看到new,我们首先就应该想到,我们分配了一块堆内存,那么指针p呢?他分配的是一块栈内存,所以这句话的意思就是:在栈内存中存放了一个指向一块堆内存的指针p。在程序会先确定在堆中分配内存的大小,然后调用operator new分配内存,然后返回这块内存的首地址,放入栈中,他在VC6下的汇编代码如下:

00401028 push 14h
0040102A call operator new (00401060)
0040102F add esp,4
00401032 mov dword ptr [ebp-8],eax
00401035 mov eax,......

阅读全文(1577) | 评论:0

C++中的指针(三) 智能指针(2007-04-21 17:21:00)

摘要:原帖及讨论:http://bbs.bc-cn.net/dispbbs.asp?BoardID=56&ID=92844 Smart Pointer是C++中的一个大题目,要说清楚他的所有好处很需要费点力气。我就一个功能一个功能的说。有我理解不透的地方希望大家指点。

1.copy-to-write
当生成一个C++ object的时候如果这个class很大,这个object会占用很多空间。那么每生成一个就占用一片空间,这样会占用很多系统资源。同时降低效率。一个解决方法就是对用拷贝构造函数生成的object,让他不存储数据,而只存储一个指向原来object数据的指针。这样空间就节省了很多。但问题在于这样两个object完全联结在了一起。如果修改了其中一个,另一个也跟着变了。所以这种方法不可取。这里讲的copy-to-write技术就是解决这类问题的方法。当通过引用一个已有object去拷贝构造新object时,新object只有一个指向已有object的指针。这两个object共享数据。直到其中一个需要修改数据的时候,再把这两块数据分离。这里举一个最简化的例子。假设一个class叫CLargeObject,里面存有很多数据。我们用一个inner class来把所有数据放在一起,叫CData。CData里面存有大量数据,例如一个数据库。这里用最简单的模型来表示,假设只有一个整数int m_nVal; CData里面需要包含另一个变量。叫作索引数目(reference count)。它记录了指向这个CData object的来自CLargetObject类的指针各数。也就是说,总共有多少CLargeObject的object正在引用着当前的CData object。

class CLargeObject
{
private:
    struct CData
    {
    private:
        int m_nVal;
     ......

阅读全文(1742) | 评论:0

C++中的指针(二) 函数指针(2007-04-21 17:20:00)

摘要:原帖及讨论:http://bbs.bc-cn.net/dispbbs.asp?BoardID=56&ID=92806 先说一下C式的函数指针。这种函数指针的应用十分广泛。
对于任何函数 void print(string s),它的指针这样定义:
void (*pfun)(string) = NULL;
pfun= &print;
或者 pfun = print;两种写法没有区别。

pfun是指针变量名。可以指向任何只带一个string参数,返回void的函数。这里让它指向print()函数。
以后调用它的时候直接写
if (pfun)
    pfun("Hello world");
C++编译器会通过pfun找到print函数,然后call  print("Hello world");
一个简单应用是可以作菜单操作。例如在文本模式下的界面,让用户选择如下操作:
"0.print, 1.copy, 2.delete, 3. quit, 4.help"
那么可以写5个函数:
void print();
void copy();
void delete();
void quit();
void help();
然后用一个函数指针数组把他们存在一起:
void (*p[])() = {print, copy, delete, quit, help};
然后根据用户入0,1,2,3,4来直接叫函数
cin >> index;
p[index]();

在windows环境下编译这种函数指针被认为是用C/C++呼叫规则(C/C++ calling convention)。就是呼叫函数caller清理函数呼叫时生成的stack。另一种规则叫标准呼叫规则(standard calling convention)。由被叫函数callee清理自己的stack。二者一般情况下区别不大,但standard calling convention更合理,因为这样使函数size变小了一点。
实际上写C/C++函数指针的时候省略了 __cdecl......

阅读全文(1716) | 评论:0

C++中的指针(一) 简单指针(2007-04-21 17:20:00)

摘要:原帖及讨论:http://bbs.bc-cn.net/dispbbs.asp?boardid=56&id=81136 简单总结一下C++中指针的用法,以后再写一篇详细的,关于smart pointer的总结。

指针的定义很简单。在变量前打个星。例如一个class的名字叫A,那么指针定义为
A  *pa;

有意点点另人混淆的是指针和const的混用。
char chr[] = "abc";
const char *p = chr; //这里p不是常数指针,而是把指针指向的地址定义为了常数。无论chr本身是不是指向常数内存区,但只要用p去操作,那么就不可以通过p去修改其内容。

chr[2] = 'e'; // ok
p[2] = 'd';  // error

p+=1; // ok, 改的是p指向的地址而不是p的内容。

真正的常数指针这么写
char *const cp = s;
这时在常数内存中allocate了一个指针的控件存储cp,cp,也就是这个地址不能改,而其指向的内存的值可以修改。
chr[2] = 'w';  //ok
cp[2] = 'y';  // ok
cp+=1;  // error

char* 可以被转换成const char*,因为操作后没有负面影响。反过来const char* 不能转换成char*,如果可以的话会把本部可写的内存的数据改掉。
// good example
char chr[] = "abc";
char *p = chr;
const char *cp = p;

// bad example
char chr[] = "abc";
const char *p = chr;
char *p = cp;  // error.

这种转换常用在函数调用上,例如strcpy(char* source, char*dest)。这个操作只是想修改source,dest只是用于参考。为了避免函数修改dest......

阅读全文(1691) | 评论:0

转:从VC++到GCC移植:谈两者语法差异(2007-04-20 22:16:00)

摘要:以下是引用片段:
  template 
  class Foo
  {
  typedef T::SomeType SomeType;
  };


  这段代码在VC++中一点问题也没有,但是GCC并不允许,因为它不知道T::SomeType是什么。你需要改为:

以下是引用片段:
  template 
  class Foo
  {
  typedef typename T::SomeType SomeType;
  };


  通过typename T::SomeType告诉GCC,SomeType是一个类型名,而不是其他东西。

  当然,这种情况不只是出现在typedef中。例如:

以下是引用片段:
  template 
  void visit(const Container& cont)
  {
  for (Container::const_iterator it = cont.begin(); it != cont.end(); ++it)
  ...
  }


  这里的Container::const_iterator同样需要改为typename Container::const_iterator。......

阅读全文(2007) | 评论:0

C++/C代码审查表(五)(2007-04-11 22:09:00)

摘要: 重要 变量值问题: (1)变量的初始化或缺省值有错误吗? (2)变量发生上溢或下溢吗? (3)变量的精度够吗?   重要 逻辑判断问题: (1)由于精度原因导致比较无效吗? (2)表达式中的优先级有误吗? (3)逻辑判断结果颠倒吗?   重要 循环问题: (1)循环终止条件不正确吗? (2)无法正常终止(死循环)吗? (3)错误地修改循环变量吗? (4)存在误差累积吗?   重要 错误处理问题: (1)忘记进行错误处理吗? (2)错误处理程序块一直没有机会被运行? (3)错误处理程序块本身就有毛病吗?如报告的错误与实际错误不一致,处理方式不正确等等。 (4)错误处理程序块是“马后炮”吗?如在被它被调用之前软件已经出错。   重要 文件I/O问题: (1)对不存在的或者错误的文件进行操作吗? (2)文件以不正确的方式打开吗? (3)文件结束判断不正确吗? (4)没有正确地关闭文件吗?  ......

阅读全文(2129) | 评论:0