C++的点点滴滴

函数传值有三种方式:按值传递(pass-by-value),按地址传递(pass-by-address)和按引用传递(pass-by-reference)。

   不同的是,按值传递方式中,函数部分不能改变主函数中实参的值。而按地址传递和按引用传递均可以改变主函数中实参的值。

按值传递,实参和形参均为同一类型的对象。例如:

void f(int b)

{...}

int main()

{

int a;

...

f(a);

}

按地址传递,实参为变量的地址,而形参为同类型的指针。

void f(int* b)

{...}

int main()

{

int a;

...

f(&a);

}

按引用传递,实参为变量,形参为同类型的引用。

void f(int& b)

{...}

int main()

{

int a;

...

f(a);

}



在C++中,通过使用运算符new和delete可以进行内存空间的动态分配和释放。比如给一个double类型的指针进行内存空间的分配和释放操作,可以采用下面的代码:


double * pd; // 先定义指针变量
pd=new double; // 然后分配内存空间
if ( pd! = NULL)
{
// 可以使用指针p d了
. . .
delete pd; // 释放分配的空间
pd = NULL;
}

如果需要分配和释放的内存空间是一个数组,可以采用下面的代码:
double * pds=new double[100]; // 定义指针变量并分配空间
if(pds != NULL)
{
// 可以使用指针p d s了
. . .
delete [] pds; // 释放分配的空间
pds = NULL ;
}

注意,当需要释放给一个数组分配的内存空间时,常常采用带中括号[ ]的形式,这样释放得会更干净。而采用不带中括号[ ]的方式,可能只释放了一部分的内存空间。
如果使用new进行内存空间的分配没有成功,则会返回空指针NULL。此外,在使用new的时候,内存的分配错误也可以被用户定义的异常处理程序所捕获。
使用new对内存的分配,可以先定义指针变量,然后为其分配内存空间(见前面的指针变量pd的使用方法)。或者在定义指针变量的同时进行内存空间的分配(见前面的指针变量pds的使用方法)。


引用作为函数参数

#include <iostream>
using namespace std;

void f(int& r) {
  cout << "r = " << r << endl;
  cout << "&r = " << &r << endl;
  r = 5;
  cout << "r = " << r << endl;
}

int main() {
  int x = 47;
  cout << "x = " << x << endl;
  cout << "&x = " << &x << endl;
  f(x); // Looks like pass-by-value,
        // is actually pass by reference
  cout << "x = " << x << endl;
}

运行结果:

x = 47
&x = 0x22ff6c
r = 47
&r = 0x22ff6c
r = 5
x = 5


指针作为函数参数
指针作为函数参数


#include <iostream>
using namespace std;

void f(int* p) {
  cout << "p = " << p << endl;
  cout << "*p = " << *p << endl;
  *p = 5;
  cout << "p = " << p << endl;
}

int main() {
  int x = 47;
  cout << "x = " << x << endl;
  cout << "&x = " << &x << endl;
  f(&x);
  cout << "x = " << x << endl;
}

运行结果:

x = 47
&x = 0x22ff6c
p = 0x22ff6c
*p = 47
p = 0x22ff6c
x = 5

void 指针

void 指针



在C++语言中,关键字void的用法比较多。比如,在函数的原型(proto type)或定义(definition)处,如果在函数名前加一个关键字void,则表示该函数没有返回值,在函数体中退出时只要使用return语句,并且后面不带返回值就可以了。如果在函数的参数列表中只有一个关键字void,则表明该函数没有参数。
void指针就是void类型的指针,即通用类型的指针。void指针在使用C++语言编程时使用范围也比较广,它指向一个地址值,但不明确说明数据类型。可以使用void指针创建通用函数:只要将函数的形参设置为void指针,就可以为参数传递各种类型的指针了。
在使用void指针的时候,通常要将指针类型转换为相应具体的数据类型。
下面的例程对void指针的定义和用法进行了具体说明。同时,本例程也可以作为一个小工具,测试各种数据类型的数在内存中是如何放置、表现的,比如,整型数在内存中排放的
顺序,是高位在前,还是高位在后;浮点数的16进制格式又是什么样子的。#
include <iostream.h>
#include <conio.h>
#include <string.h>
#include <stdio>
#pragma hdrstop

#pragma argsused

void showhex(void *pv, int size)
{
        char *pc = new char[100]; // 临时字符数组,获取整个数据的1 6进制表示
        char *temppc = new char[10]; // 临时字符数组,获取整个数据的1 6进制表示
        if((pc != NULL)&&(temppc != NULL))
        {
         strcpy(pc,""); //清空
                for (int i=0; i< size; i++) // 根据数据的字节长度来循环
                {
                 sprintf(temppc, "%01X ",((unsigned char *)pv)[i]);
                        strcat(pc,temppc);
                }
                cout<<pc<<endl; // 显示1 6进制方式的数据并回车
        }
}

int main(int argc, char* argv[])
{
        void *pv = NULL; // 定义void类型的指针

        unsigned int i1 = 0X12345678;
        pv = &i1;
        showhex(pv, sizeof(i1));
        return 0;
}

运行结果

78 56 34 12


typeid运算符

可以使用typeid运算符来求得变量或对象的数据类型。typeid运算符对变量或数据类型作用后,将返回一个type_info类型的对象,通过该对象,就可以获取数据类型的名称。type_info类一般在头文件typeinfo.h中定义。在C++中,相应类的定义如下:
class type_info
{
    public :
        _CRTIMP virtual ~type_info();
        _CRTIMP int operator==(const type_info& rhs) const;
        _CRTIMP int operator!=(const type_info& rhs) const;
        _CRTIMP int before(const type_info& rhs) const;
        _CRTIMP const char* name() const;
        _CRTIMP const char* raw_name() const;
    private :
        void *_m_data;
        char _m_d_name[1];
        type_info(const type_info& rhs);
        type_info& operator=(const type_info& rhs);
} ;
可以看到,通过type_info类的name成员函数就可以获取指定变量或对象的数据类型。
下面通过例程S 0 2 H说明typeid的一些用法。

例程S 0 2 H的源代码
#include <conio.h>
#include <iostream.h>
#include <typeinfo.h>
struct Car // 定义结构C a r
{
    double Price; // 价格
    double Length; // 长度
} ;
void main()
{
    int i; // 整数类型
    const type_info & t0=typeid(i); // 获取变量的数据类型
    cout<<"i的类型为: "<<t0.name()<<endl; // 显示变量的数据类型
    Car c1; // 结构
    const type_info & t1=typeid(c1); // 通过typeid操作符来生成
    type_info引用
    const type_info & t2=typeid(Car);
    // 比较变量与结构的数据类型是否一致
    if(t1 == t2)
        cout<<"c1的类型为:"<<t1.name()<<endl;
    else
        cout<<"c1与struct Car的类型不匹配。"<<endl;
    getch();
}

运行结果:
i的类型为: int
c1的类型为:struct Car
说明:
typeid运算符不区分变量是否有const修饰符,也不区分变量的存储类型,同时也不区分是否是引用重载函数

同模板函数类似,重载函数的机制也可以定义一系列功能相似的函数,并且这一系列函数的函数名是相同的,只是参数列表和返回值不同。通常将这一系列的函数称为一个重载函数家族。
如果两个函数只是返回值不同,则不可以作为一个重载函数家族中的两个不同的函数。存在于同一个重载函数家族中的各个函数,要么是形参的个数不一样,要么是形参的数据类型不一样。
在调用重载函数的时候,将根据函数的实参与定义的一系列重载函数的形参进行类型匹配,如果与其中某个的类型完全匹配,则调用该函数。如果没有完全匹配的重载函数,则可能对实参进行数据类型的转换,以适应其中的一个重载函数。
在建立重载函数的时候要考虑下面一些因素的影响:
• 形参设置有默认数值。
如果函数的形参设置有默认数值,则调用函数的时候,实参的个数是不定的。
• 参数列表中参数个数不定。
由于使用了省略号"...",将导致参数列表中参数个数不定。
• 模板函数。
模板函数也可以定义一组功能相似的函数家族。
• 数据类型转换。
在定义和使用重载函数的时候要注意函数实参的类型转换。有时数据类型的转换也会耗费一定的C P U时间。
在进行函数重载的时候,需要考虑的因素比较多。但总的说来,如果编译器找不到参数列表可以匹配的函数时就会报错;如果编译器找到有歧义的多重匹配,也会出现错误。所以,只要避免了上面的两种错误,一般就没有多大的问题了。
下面通过例程S 0 2 L简单说明重载函数的定义与用法。

例程S 0 2 L的源代
#include <iostream.h>
#include <conio.h>
// 求两个整数的最大值
int max( int x,int y)
{
    cout <<"int type:";
    return x>y?x:y;
}
// 求两个浮点数的最大值
double max( double x,double y)
{
    cout<<"double type: ";
    return x>y?x:y;
}
void main()
{
    cout<<max(1, 2)<<endl;
    cout<<max(3.0, 5.0)<<endl;
    // 下面一行错误,故屏蔽掉
    // cout<<max(3,5.0)<<endl; // 存在两种转换的可能
    getch();
}

运行结果:
int type:2
double type:5


。有兴趣的读者可以自己修改上面的例程,测试一下。

const类型成员函数与mutable 


const类型的成员函数是指使用const关键字进行修饰的类的成员函数。const类型的成员函数对函数内部的操作加以一定的限制,比如不可以对对象的属性进行修改等,这样可以提高程序代码的正确性。
在使用关键字const对成员函数修饰的时候,将const放在成员函数的后面。在定义对象的时候,可以使用关键字const声明对象为常量对象。对于常量对象,如果使用非常量成员函数来操作,则会出现一个编译错误;同样,对于使用关键字volatile声明的对象,
如果使用非volatile类型的成员函数来操作,也会出现一个编译错误。
如果想修改const类型对象的成员变量,可以使用关键字mutable对该成员变量进行修饰。

#include <iostream.h>
#include <conio.h>
class CDate
{
   public:
   int year;
   mutable int month; // 使用关键字mutable进行修饰
   CDate(int y=2000,int m=1)
   {
      year= y;
      month= m;
   };
   int GetMonth() const; // 一个只读函数
   void SetMonth( int m ); // 一个写函数
};
int CDate::GetMonth() const
{
   // 下面一行错误,故屏蔽掉
   // year++; // 但如果是month+ + ;则没问题
   return month; // 在只读函数中不可以进行改写操作
}
void CDate::SetMonth(int m)
{
   month = m; // 设置月份
}
void main()
{
   CDate d1;
   d1.Setmonth(9);
   d1.year=1975 ;
   cout<<"d1:"<<d1.year<<"年"<<d1.Getmonth()<<"月/n";
   const CDate d2;
   d2.month=7;
   cout<<"d2:"<<d2.year<<"年"<<d2.Getmonth()<<"月/n";
   // 下面一句错误,故屏蔽掉
   // d2.SetMonth(7); // 不可以对常量对象调用非常量成员函数
   // 下面一句错误,故屏蔽掉
   // d2.year=1973; // 不可以修改常量对象的成员变量
}
运行结果:
d1:1975年9月
d2:2000年7月
说明:
• 在上面的CDate类中,成员变量year为普通类型的变量,而成员变量month为mutable类型的成员变量,所以成员变量month不受const的约束,这可以从上面例程中的多个方面表现。
• 在const类型的成员函数定义体中,不可以直接或间接地修改普通类型的成员变量(mutable类型的成员函数除外)。当对象被声明为const类型后,一般也不可以直接或间接地修改对象的成员变量( mutable类型的成员函数除外)。


 静态成员变量与静态成员函数 

 

在类中可以使用关键字static定义静态成员变量和静态成员函数,它们都具有特殊的用途。
当为一个类定义了一个静态成员变量后,该变量对于该类的所有对象来说,只有一个拷贝,也就是为该类的所有对象共用。基于此,静态成员变量可以用来表示一些特殊的属性,比如对于该类当前的对象来说,该属性必须是一致的。其中的某一个对象修改了该属性,则所有其他对象的该属性值都被修改了。也可以说,类的静态成员变量是一个公共变量,只不过公用的范围只限于该类的对象。
静态成员变量可以初始化和赋值。在初始化和赋值时只要利用作用域运算符“ : :”指定类名即可。对于私有类型的静态成员变量可以象公有类型的静态成员变量一样赋值,但是不可以直接引用。
另外,可以使用静态成员变量来表示当前该类所有对象的个数。在一开始将静态成员变量清零,并在类的构造函数中将此静态成员变量增加1,在析构函数中将此变量减1。这样,就可以随时确定当前对象的个数。
静态成员函数中实现的代码是一些与具体对象无关的代码,是来实现该类全体对象通用的任务。

所以,对于静态成员函数有下面的一些约束:
• 静态成员函数的定义体中不可以对非静态成员变量进行操作。
• 静态成员函数中不可以调用非静态成员函数。
• 静态成员函数中不能使用this指针。
通常,构造函数和析构函数不可以是静态成员函数。
下面通过例程说明静态成员变量和静态成员函数的用法。

#include <iostream.h>
#include <conio.h>
class Rabbit
{
   public:
   Rabbit(); // 默认构造函数
   ~ Rabbit(); // 析构函数
   static int GetNum(); // 公有类型的静态成员函数
   static int Num; // 公有类型的静态成员变量
};
Rabbit::Rabbit()
{
   Num++;
}
Rabbit::~ Rabbit()
{
   Num--;
}
int Rabbit::GetNum()
{
   return Num;
}
int Rabbit::Num=0; // 对静态公有成员变量进行初始化
void main()
{
   Rabbit r1,r2; // 将调用默认的构造函数
// 公有类型的静态成员变量可以直接引用
   cout<<"Num of Rabbits is "<<Rabbit::Num<<"./n";
   Rabbit * r3=new Rabbit; // 将调用默认的构造函数
// 通过类调用静态成员函数
   cout<<"Num of Rabbits is "<<Rabbit::GetNum()<<"./n";
   delete r3; // 将调用唯一的一个析构函数
// 通过对象调用静态成员函数
   cout<<"Num of Rabbits is "<<r1.GetNum()<<"./n";
}
运行结果:
Num of Rabbits is 2.
Num of Rabbits is 3.
Num of Rabbits is 2.
说明:
静态成员函数的调用有可以有下面两种方法:
• 类名.函数名( );
• 对象名.函数名( );
通常建议使用第一种方式,而不要使用第二种方式,因为后者的可读性不好。
 
对象的引用参数传递 

 

下面的例程将对“对象的引用参数传递”与“对象的普通参数传递”作一个定量的比较:
首先定义两个不同参数传递类型的函数,然后在主函数中获取系统时间并显示,接着多次调用使用引用参数传递的函数,最后再一次获取系统时间并显示。将运行结果记录下来后,修改代码,调用使用普通参数传递的函数,运行后记录下运行结果,与前面的记录进行比较。
例程中对如何获取系统时间也进行了说明。

#include <iostream.h>
#include <stdio.h>
#include <conio.h>
#include <sys/timeb.h>
#include <time.h>
class Book // 类的定义体
{
    public:
        int No;
        Book(int n); // 构造函数
        ~ Book() ; // 构造函数
} ;
Book::Book(int n)
{
    No=n;
}
Book::~ Book ( )
{
}
void Add(Book b) // 普通参数传递
{
    b.No++;
}
void AddRef(Book & b) // 引用参数传递
{
    b.No++;
}
void main()
{
    Book B1(1); // 临时变量
    long i;
    struct timeb CurTime;
    char TimeMes[100];
    char *timeline;
// 获取计算前的开始时间
    ftime( &CurTime);
    timeline = ctime( & ( CurTime.time ) );
    cout<<timeline<<endl;      //当前时间 如"Fri Dec 24 13:39:29 2004"
    sprintf(TimeMes,"计算开始时间是:%.19s.%hu %s ", timeline,CurTime.millitm,&timeline[20]);
    cout<<TimeMes;
// 计算内容
    for(i=0;i<10000000;i++)
        AddRef(B1);// 修改此句为:Add(B1);
    cout<<B1.No<<endl;
// 获取计算后的结束时间
    ftime( &CurTime);
    timeline = ctime(&(CurTime.time));
    sprintf(TimeMes,"计算结束时间是:%.19s.%hu %s ", timeline,CurTime.millitm,&timeline[20]);
    cout<<TimeMes;
    getch();
}

运行结果:

Fri Dec 24 13:46:57 2004

计算开始时间是:Fri Dec 24 13:46:57.20 2004
 10000001
计算结束时间是:Fri Dec 24 13:46:57.90 2004

指向函数的指针 

 
通过指向函数的指针,可以编写一些通用性较好的代码。
下面的例程S02M中定义了两个普通的函数,一个是求出两个整数中的最大值,另一个是求出两个整数中的最小值。例程中还定义了一个指向函数的指针,指针的类型及参数列表中各参数的类型与前面定义的两个函数是一致的,最后在主函数中通过该指针实现了一定的操作。
例程S02M的源代码
#include <iostream.h>
#include <conio.h>
// 判断两个整数的最大值
int Max(int x,int y)
{
 return x>y?x:y;
}
// 判断两个整数的最小值
int Min(int x,int y)
{
 return x<y?x:y;
}
// 定义指向函数的指针
int (* m_pFunction)(int,int);
void main()
{
 m_pFunction = Max; // 指向求最大值的函数
 cout <<"2和3中较大的是:"<<(*m_pFunction)(2,3)<<endl;
 m_pFunction = Min; // 指向求最小值的函数
 cout <<"2和3中较小的是:"<<(*m_pFunction)(2,3)<<endl;
 getch();
}
运行结果:
2和3中较大的是: 3
2和3中较小的是: 2
说明:
如果函数指针需要指向的函数有参数列表,那么在定义指针的时候,可以不必指明形参,只要一个数据类型的列表就可以了。

 
 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值