宝杉的博客

UNIX/LINUX;ACE;SNMP;C++
posts - 33, comments - 23, trackbacks - 0, articles - 0

缺省值

提高程序易用性,避免每次调用函数参数都相同的情况。

缺省值只能出现在函数声明中,不能在函数定义中。

两个原因:

一是函数的实现(定义)本来就与参数是否有缺省值无关,所以没有必要让缺省值出现在函数的定义体中。

二是参数的缺省值可能会改动,显然修改函数的声明比修改函数的定义要方便。

规则:参数从后向前缺省

正确的示例如下:

void Foo(int x, int y=0, int z=0);

错误的示例如下:

void Foo(int x=0, int y, int z=0);  

但要避免二义性

C++exams\default_para

 

运算符重载及其规则

运算符即可定义为成员函数,也可定义为全局函数。

规则如下:

运算符

规则

所有的一元运算符

建议重载为成员函数

= () [] ->

只能重载为成员函数

+= -= /= *= &= |= ~= %= >>= <<=

建议重载为成员函数

所有其它运算符

建议重载为全局函数

 

不能重载的运算符

1)不能改变C++内部数据类型(如int,float等)的运算符。

2)不能重载‘.’,因为‘.’在类中对任何成员都有意义,已经成为标准用法。

3)不能重载目前C++运算符集合中没有的符号,如#,@,$等。原因有两点,一是难以理解,二是难以确定优先级。

4)对已经存在的运算符进行重载时,不能改变优先级规则,否则将引起混乱。

 

函数内联

目的:提高执行效率。

Q为什么要用?

A提高执行效率。

Q如何提高?

A宏代码本身不是函数,但使用起来象函数。预处理器用复制宏代码的方式代替函数调用,省去了参数压栈、生成汇编语言的CALL调用、返回参数、执行return等过程,从而提高了速度。

Q缺点?

使用宏代码最大的缺点是容易出错,预处理器在复制宏代码时常常产生意想不到的边际效应。

 

 

例如:

#define MAX(a, b)       (a) > (b) ? (a) : (b)

语句

result = MAX(i, j) + 2 ;

将被预处理器解释为

result = (i) > (j) ? (i) : (j) + 2 ;

由于运算符‘+’比运算符‘:’的优先级高,所以上述语句并不等价于期望的

result = ( (i) > (j) ? (i) : (j) ) + 2 ;

如果把宏代码改写为

#define MAX(a, b)       ( (a) > (b) ? (a) : (b) )

则可以解决由优先级引起的错误。但是即使使用修改后的宏代码也不是万无一失的,例如语句result = MAX(i++, j);

将被预处理器解释为

result = (i++) > (j) ? (i++) : (j);

对于C++ 而言,使用宏代码还有另一种缺点:无法操作类的私有数据成员

posted @ 2007-08-13 10:21 宝杉 阅读(179) | 评论 (0)编辑 收藏

隐藏规则:

“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。

2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

 

回顾

静态多态性:函数重载;运算符重载;

动态多态性:继承;虚函数;

 

上述例子之所以使用指针目的是:表达一种动态性质,即当指针指向不同对象可以调用不同方法。但事实上不能达到这样的效果。虽然可以强制指针的类型转换((derived *p->g();),或者直接调用指向派生类的指针,但是只要将成员函数声明为virtual就能起到这样的作用。

隐藏

不同指针指向同一地址,那么指针的类型决定调用方法,还是指针指向的对象?

例子:C++exams\hide

可以看出,virtual函数可以实现不同类型的指针指向不同对象,调用不同的方法。

就是说,当父类对象和子类对象同时存在,且调用父子对象的同名方法时,只能用虚拟函数实现。

 

另外,提供一种思路:

如果基类与派生类的同名函数参数不同,比如:

class Base

{

public:

void f(int x);

};

class Derived : public Base

{

public:

void f(char *str);

};

void Test(void)

{

Derived *pd = new Derived;

pd->f(10);  // error

}

基类为int,派生类为string.如果想在派生类中调用基类的方法,即在派生类中添加基类方法的调用。

class Derived : public Base

{

public:

void f(char *str);

void f(int x) { Base::f(x); }

};

但此做法容易混乱,不宜使用。最好的方法还是用virtual

 

指针类型

对象类型

面向对象动态的设计,应该是对象类型起到关键作用,而不是指针类型。

posted @ 2007-08-13 10:16 宝杉 阅读(120) | 评论 (0)编辑 收藏

重载与覆盖

成员函数被重载的特征:

1)相同的范围(在同一个类中);

2)函数名字相同;

3)参数不同;

4virtual关键字可有可无。

覆盖是指派生类函数覆盖基类函数,特征是:

1)不同的范围(分别位于派生类与基类);

2)函数名字相同;

3)参数相同;

4)基类函数必须有virtual关键字。

例子:C++exams\cover

指向基类的指针可以指向其共有派生类的对象,但反之不行,另外私有派生类也不行。

比如:车 是基类,小汽车,马车 等是派生类。

一个车类的指针可以指向任何派生类,因为它们都属于车。

而反之,不能说指向马车的指针可以指向车,因为车的概念最大。

posted @ 2007-08-13 10:15 宝杉 阅读(143) | 评论 (0)编辑 收藏

重载(overloaded)、内联(inline)、constvirtual

 

 

重载

内联

const

virtual

全局函数

 

 

类的成员函数

内部标识符

编译器根据参数为每个重载函数创建内部标识符,以便区分忽略返回值与有返回值的重载函数。

连接交换指定符号

C编译过的函数,经过编译器标识后与C++的表示风格不同。所以C++不能直接调用C编译出的函数。C++提供extern “C”

例如:

extern “C”

{

   void foo(int x, int y);

   … // 其它函数

}

或者写成

extern “C”

{

   #include “myheader.h”

   … // 其它C头文件

}

 

全局函数与成员函数同名

全局函数与成员函数同名不算重载,因为函数作用域不同。

为了区别,调用全局函数时,注意格式:

::函数名(参数);

 

隐式类型转换导致重载函数产生二义性

隐式类型转换:数字本身没有类型,把数字当作参数,自动进行类型转换。

例如:

void output( int x);   // 函数声明

void output( float x); // 函数声明

output(0.5)将产生编译错误,因为编译器不知道该将0.5转换成int还是float类型的参数。

正确写法:

     output(int(0.5));  // output int 0

     output(float(0.5));    // output float 0.5

 

posted @ 2007-08-13 10:14 宝杉 阅读(323) | 评论 (0)编辑 收藏

freedelete

只是把指针所指的内存给释放掉,但并没有把指针本身干掉。

 

注意:

1)指针消亡了,并不表示它所指的内存会被自动释放。

2)内存被释放了,并不表示指针会消亡或者成了NULL指针。

比较

mallocfree:无法满足动态对象的要求。对象在创建同时进行构造,消亡同时析构。

malloc free是库函数,不是运算符,不受编译器控制。

newdelete:不是库函数,能动态内存分配和清理内存。

 

内存耗尽

内存耗尽时newmalloc返回NULL。但在WIN32下,使用虚拟内存,不会耗尽的。

处理内存耗尽大概有两种。

A  *a = new A;

1       一处内存申请

if(a == NULL)

{

     return;

     }

2    如果有多处申请内存,则

if(a == NULL)

{

     cout << Memory Exhausted << endl;

     exit(1);

}

 

newdelete使用要点

1 new内置了sizeof、类型转换和类型安全检查功能。

2 new在创建非内部数据类型的动态对象的同时完成了初始化工作。

3 对象有多个构造函数,那么new的语句也可以有多种形式。

例如:diary files\obj.txt

4 如果用new创建对象数组,那么只能使用对象的无参数构造函数。例如

     Obj  *objects = new Obj[100];    // 创建100个动态对象

不能写成

     Obj  *objects = new Obj[100](1);// 创建100个动态对象的同时赋初值1

5在用delete释放对象数组时,留意不要丢了符号‘[]’。例如

     delete []objects;  // 正确的用法

delete objects;    // 错误的用法

后者相当于delete objects[0],漏掉了另外99个对象。

posted @ 2007-08-13 10:12 宝杉 阅读(392) | 评论 (0)编辑 收藏

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。

指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。

1 修改内容

       char a[] = "hello";

       a[0] = 'X';

       cout << a << endl;

       char *p = "world";     // 注意p指向常量字符串

       p[0] = 'X';                 // 编译器不能发现该错误

       cout << p << endl;


2 复制与比较

strcpy(new type[strlen(a)+1]),而不能用p = a    //把a的地址给了p,而不是a的内容

strcmp(if strcmp(a,p)== 0),而不能用      if( a = p)  

       // 数组…

       char a[] = "hello";

       char b[10];

       strcpy(b, a);                 // 不能用       b = a;

       if(strcmp(b, a) == 0)     // 不能用  if (b == a)

       // 指针…

       int len = strlen(a);

       char *p = (char *)malloc(sizeof(char)*(len+1));

       strcpy(p,a);                  // 不要用 p = a;

       if(strcmp(p, a) == 0)     // 不要用 if (p == a)

sizeof内存容量计算

1           sizeof(p)相当于sizeof(char*),C++不支持对指针所指内容容量的计算。

char a[] = "hello world";

    char *p  = a;

    cout<< sizeof(a) << endl;   // 12字节

    cout<< sizeof(p) << endl;   // 4字节

2           数组作为函数参数,退化成同类型指针。

void Func(char a[100])

    {

        cout<< sizeof(a) << endl;   // 4字节而不是100字节

}

指针参数传递内存

首先,考虑函数为参数创建临时副本的问题。对于值传递,有形参和实参的区别。但对于引用和指针传递,则可能会产生问题。

 

指针作为函数参数,不能动态申请内存。

void GetMemory(char *p, int num)

{

       p = (char *)malloc(sizeof(char) * num);

}

void Test(void)

{

       char *str = NULL;

       GetMemory(str, 100);           // str 仍然为 NULL     

       strcpy(str, "hello");               // 运行错误

}

毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。

如何改正:

1 用“指向指针的指针”

void GetMemory2(char **p, int num)          //**p

{

       *p = (char *)malloc(sizeof(char) * num);

}

void Test2(void)

{

       char *str = NULL;

       GetMemory2(&str, 100);                    // 注意参数是 &str,而不是str

       strcpy(str, "hello");

       cout<< str << endl;

       free(str);

}

2 用函数返回值来传递动态内存

char *GetMemory3(int num)

{

       char *p = (char *)malloc(sizeof(char) * num);

       return p;

}

void Test3(void)

{

       char *str = NULL;

       str = GetMemory3(100);      

       strcpy(str, "hello");

       cout<< str << endl;

       free(str);

}

注意

强调不要用return语句返回指向“栈内存”的指针

char *GetString(void)

{

       char p[] = "hello world";

       return p;  // 编译器将提出警告

}

void Test4(void)

{

char *str = NULL;

str = GetString();   // str 的内容是垃圾

cout<< str << endl;

}

posted @ 2007-08-03 13:08 宝杉 阅读(186) | 评论 (0)编辑 收藏

内存分配方式

1 静态存储区域           全局变量 static

2 栈                            局部变量

3 动态存储区域(堆)(malloc free) (new delete)

 

常见错误和对策

1 内存分配未成功。

对策:程序入口处,检查指针是否为NULL。

(1)参数*p所指向的内存,用assert( p != NULL)

(2)malloc或new申请的内存,用if ( p != NULL)

2 内存未初始化。

3 内存操作越界。

4 忘记释放内存,内存泄露。

new与delete配对。

5 释放内存,却继续使用。

       (1)return不能返回“栈内存指针”和“引用”,因为该内存在函数结束时被销毁。

       (2)释放内存后,设为NULL,防止“野指针”。

规则

1 申请内存后检查。assert( p != NULL)

2 数组、动态内存初始化。

3 申请释放要配对。

4 释放内存设置NULL,防止产生野指针。

posted @ 2007-08-03 13:06 宝杉 阅读(170) | 评论 (0)编辑 收藏

引用的主要作用:传递函数的参数和返回值。

C++语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。

值传递

函数内的形参是实参(外部变量)的一个拷贝,所以不会影响实参(外部变量)的值。

    void Func1(int x)

{

    x = x + 10;

}

int n = 0;

    Func1(n);

    cout << “n = ” << n << endl;  // n = 0

指针传递

    void Func2(int *x)

{

    (* x) = (* x) + 10;

}

int n = 0;

    Func2(&n);

    cout << “n = ” << n << endl;      // n = 10

引用传递

    void Func3(int &x)

{

    x = x + 10;

}

int n = 0;

    Func3(n);

    cout << “n = ” << n << endl;      // n = 10

指针功能强大,但非常危险。恰如其分的使用“引用”,发挥其作用。

一般先考虑“引用”,如果“引用”不能做的事,则再用“指针”来完成。

posted @ 2007-08-03 13:05 宝杉 阅读(181) | 评论 (0)编辑 收藏

pragma就是为了让编译器编译出的C或C++程序与机器硬件和操作系统保持完全兼容而定义的宏扩展,#pragma是和特定编译器相关的。

 

一、Pragma说明(Directives)

C和C++程序的每次执行都支持其所在的主机或操作系统所具有的一些独特的特点。
一些程序,例如,需要精确控制数据存放的内存区域或控制某个函数接收的参数。
#pragma指示为编译器提供了一种在不同机器和操作系统上编译以保持C和C++完全兼容的方法。Pragmas是由机器和相关的操作系统定义的,通常对每个编译器来说是不同的。

 

二、语法(Syntax)

#pragma token-string(特征字符串)特征字符串是一连串的字符,就是要给一个特定编译器提供说明和编译意见。

符号(#)必须是pragma所在那一行的第一个非空格字符;
#号和pragma之间可以有任意个空格符。
在#pragma之后,是可以被编译器解析的预处理特征字符。
一般认为,#pragma属于宏扩展。
如果编译器发现不认识的pragma,会提出警告,但继续编译下去。

Pragmas可以用在条件声明上,提供最新的功能性的预处理程序,或者提供给编译器定义执行的信息。

其格式一般为: #pragma  para 

其中para为参数,下面来看一些常用的参数。


alloc_text
comment //注释
init_seg1 
optimize  //最优化
auto_inline
component  //组成部件
inline_depth
pack       //包
bss_seg
data_seg
inline_recursion  //内嵌递归
pointers_to_members1
check_stack
function   
intrinsic  //内在的
setlocale
code_seg
hdrstop
message 
vtordisp1
const_seg
include_alias
once
warning


 

参数详解

diary files\#pragma  预处理指令详解.doc

posted @ 2007-08-03 13:04 宝杉 阅读(267) | 评论 (0)编辑 收藏

   * 非递归锁:非递归锁提供互斥的一种高效的形式,它定义一个临界区,每一时刻只有单个线程可在其中执行。它们之所以是非递归的,是因为当前拥有锁的线程在将其释放前不可以再次获取它。否则,就会立即发生死锁。SunOS 5.x通过它的mutex_trwlock_t,和sema_t类型(POSIX Pthreads不提供后两种同步机制)为非递归锁提供支持。ASX构架提供MutexRW_Mutex,和Semaphore包装,以分别封装这些语义。

   * 递归锁:另外一方面,递归锁允许acquire方法嵌套调用,只要当前拥有该锁的线程就是试图重新获取它的线程。递归锁对于回调驱动的事件分派构架(比如1.3.2描述的反应堆)特别有用,在其中构架的事件循环执行对预登记的用户定义的对象的回调。因为随后用户定义的对象可能经由它的方法入口重入分派构架,必须使用递归锁以防止在回调过程中构架持有的锁发生死锁。

posted @ 2007-08-03 13:03 宝杉 阅读(3567) | 评论 (2)编辑 收藏

仅列出标题
共4页: 1 2 3 4