C++基础细节2

来源:互联网 发布:网络技术交流 编辑:程序博客网 时间:2024/05/22 06:31

1.关于引用和指针

概念上,引用(&)并不是对象,而是一个已经存在的对象的别名;引用不可以重新绑定到另外一个对象,因此引用必须初始化。(类比const,一经定义就不能修改,所以必须初始化,是同样的道理。)

引用和指针都是实现了其他对象的间接访问。不同的是:指针本身就是一个对象,允许对指针进行赋值和拷贝;指针无需在定义时赋初值。

对于引用的概念,通过这段代码加深印象:

    int ival=12;    int *p=&ival;    int &refval=ival;    int *p1=&refval;

int &refval=ival;中的&是代表引用声明符号;而int *p1=&refval;的&则代表取地址

如下图的watch中所示,可以看出p和p1这两个指针的值(所存放的地址值0x0039F1D8),以及这两个指针所指向的值(12)是完全一样的(其实有点废话,既然两个指针都是指向同一个地址,自然指向的值是一样的),也就是ival和它的引用refval是等价的。

注意:虽然指针p1初始化为&refval,而&refval和&ival这两个值是不一样的,也就是refval和ival是存放在内存中的不同地址上,但是int *p1=&refval这句初始化之后,p1指针上存的值其实并不是refval的地址,而是refval所引用的变量ival的地址。因此,这里就更明确了:引用的作用就相当于代言者,这个代言者所说所做的一切都代表了原始对象,与引用自身无关。

由于引用不是对象,所以不存在指向引用的指针。我想基本上这也上面那句int *p1=&refval之后,p1的值竟然是ival的地址的概念上的辅证。

但是存在指针的引用。

2.关于const

如果利用一个对象去初始化另外一个对象,则他们是否是const都无所谓。常量特性仅仅用于限定其初始化之后不可修改

如果想在多个文件中共享const对象,必须在变量的定义之前添加extern关键字。

3.C风格字符串

 C风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法,按此习惯书写的字符串存放在字符数组中并用空字符('\0')结束。

char ca[] = { 'C', '+', '+' };
cout << strlen(ca) << endl;

上面的示例中,ca虽然是一个字符数组,但是它并没有以'\0'结束,因此这段代码输出的结果不可知。strlen函数在执行的时候可能沿着ca在内存中的位置不断向前寻找,直到遇到'\0'才结束。

而只有这样:

char ca[] = { 'C', '+', '+', '\0' };
cout << strlen(ca) << endl;

才能保证输出的结果是3.

    再定义两个字符数组来说明:

char ca1[] = "string 1";
char ca2[] = "string 2";

由于在使用数组的时候,其实真正使用的是指向数组的首元素的指针。因此我们不能使用if(ca1<ca2)这样的语句进行这两个字符数组的比较;也不能使用ca1+ca2这样的语句进行字符串的串联操作

我们必须使用strcmp进行字符串比较操作;使用strcpy和strcat进行字符串的拷贝和连接操作,而且在strcpy和strcat函数的使用的时候,我们要非常仔细的检查字符数组的容量:

    char sumStr[40];//注意数组容量,调试的时候又想起了最近看到的关于“烫烫烫烫”的冷笑话    strcpy(sumStr, ca1);    strcat(sumStr, "-");    strcat(sumStr, ca2);

 

(ps:在vs2013上进行编译的时候,编译器直接告诉我:'strcpy': This function or variable may be unsafe. Consider using strcpy_s instead

    char sumStr[40];    strcpy_s(sumStr, ca1);    strcat_s(sumStr, "-");    strcat_s(sumStr, ca2);

 

需要说的是:strcpy_s、strcat_s是VS后续版本中微软新推出的更安全的函数,并非标准库里面的。所以还是建议使用string以保证可移植

4.函数返回类型

函数返回类型不能是数组类型或者函数类型,但是可以是指向数组或者函数的指针

5.通过引用避免拷贝

针对函数形参,拷贝大的类类型对象或者容器时效率比较低,甚至有的类型(比如IO类型)根本不支持对象拷贝,这种情况就必须使用引用参数。另外,为了避免在函数内修改实参,我们可以使用常量引用。

bool cmp_length(const string &s1, const string &s2){    return s1.size() > s2.size();}

另外,比较好的习惯是:&,*符号和参数名写在一起,不要和类型连写,以免理解上的误会。

6.类

构造函数:

构造函数不能使用const。只有在类没有声明任何构造函数的时候,编译器才会自动生成默认无参构造函数。因此,一旦我们定义了其他默认构造函数,那么除非我们在定义一个无参数的构造函数,否则类将没有默认构造函数。如果类包含有内置类型或者复合类型的成员,那么只有在这些成员全部都赋予了类内的初始值的时候,这个类才适合于使用合成的默认构造函数。C++11新标准允许使用=default来要求编译器生成默认构造函数。

大多数情况下,使用构造函数初始化列表,或者提供参数,在构造函数的函数体中使用赋值语句在效果上没有什么区别。但是针对const、引用,这种则必须通过构造函数初始化来处理。

如下这段处理就是错误的:

复制代码
class A{public:    A()    {//提示报错:常量成员b和引用成员c没有提供初始值        b = 0;//错误,只读根本不能修改        c = 0;//没有初始化    }private:    int a;    const int b;    int &c;};
复制代码

 

正确的方式是使用构造函数初始化列表:

复制代码
class A{public:    A(int x) :b(x), c(x)    {    }private:    int a;    const int b;    int &c;};
复制代码

 

 

友元(friend):

友元的声明仅仅是制定了访问的权限,而并不是一个通常意义上的函数声明。如果我们希望类的用户能够调用某个友元函数,那么我们必须在友元生命之外再专门这个函数进行一次声明。虽然许多编译器并不强制限定友元函数必须在使用之前在类外声明,但是最好还是提供一个函数的独立声明,尽量别让程序依赖于编译器。

友元除了可以用于普通的非成员函数,也可以用于类、以及类的成员函数。另外,友元函数不存在传递性。比如说类A中声明了类B是A的友元,类B中声明了类C是B的友元,我们不能仅仅根据这个就认为C是A的友元。也就是说每个类需要自己控制自己的友元类和友元函数

友元函数可以定义在类的内部。不过就算将友元函数的定义放在类的内部,我们也必须先在函数外面有对这个函数的声明之后才能对这个友元函数进行调用。如下代码:

复制代码
class X{    friend void f()    {        //函数体    }    X(){ f(); } //error C3861 : “f” : 找不到标识符    void g();};void X::g(){ f(); } //error C3861 : “f” : 找不到标识符void f();
复制代码

如果将f的声明放在类X定义之前就不会报错了。

复制代码
void f();//声明提前class X{    friend void f()    {        //函数体    }    X(){ f(); } //编译通过    void g();};void X::g(){ f(); } //编译通过
复制代码

 

 

可变数据成员:

有些情况下,我们希望能在即使是const成员函数中也能够修改某个数据成员,可以通过在声明变量的时候加上mutable关键字做到这一点:

复制代码
class Screen{public:    void some_member() const;    void print_count();private:    mutable size_t count = 0;};void Screen::some_member() const{    ++count;}void Screen::print_count(){    cout << count << " ";}int main(array<System::String ^> ^args){    Console::WriteLine(L"Hello World");        Screen item = Screen();    item.print_count();//输出0    item.some_member();    item.print_count();//输出1}
复制代码

作用域:

如下代码,可以通过类名::成员变量,或者::变量的方式来强制访问

复制代码
int param = 2;class Scorp{public:    void print(int param)    {        cout << param << endl;//形参作用域        cout << Scorp::param << endl;//类中作用域,同this->param        cout << ::param << endl;//类外作用域    }private:    int param = 1;};int main(array<System::String ^> ^args){    Console::WriteLine(L"Hello World");    Scorp scorp = Scorp();    scorp.print(3);//【param】输出3,【Scorp::param】输出1,【::param】输出2}
复制代码

转换构造函数:

如果构造函数只接受一个参数,那么存在一种这个类型的隐式转换机制:从构造函数的参数类型类类型隐式转换。如下代码:

复制代码
class A{public:    A(int param)    {        a = param;    }    const void print(const A &item)const    {        cout << item.a << "【注意另一个a】--> " << a << endl;    }private:    int a;    int b;    int c;};int main(array<System::String ^> ^args){    A a_instance = A(6);    a_instance.print(9);}
复制代码

输出结果是:9【注意另一个a】--> 6。也就是说,在print函数执行的时候,item.a等于9,a等于6。

注意这里发生了两次构造函数的执行,第一次是定义a_instance并且初始化的时候这时候param为6;第二次是调用a_instance.print(const A &item)函数的时候,特别要注意的是:这里的形参是一个常量引用,因此这里可以使用隐式转化机制,传入A的单个参数的构造函数的形参(整型),用这个方式来隐式处理:编译器通过给定的int型的9自动调用对应的构造函数创建了一个A类型的对象,生成的这个临时对象传给了print函数,并且作为item在函数体中使用,自然item.a的值就是9。

另外要注意的是这里我们的参数使用的int这个内置类型,所以处理的时候能够使用print(9)这种字面量形式的参数。如果构造函数的唯一参数不是内置类型,是其它的比如string,那么这里在处理的时候就不能直接写成print("9"),而是要分成两句书写:

string str="9";

a_instance.print(str);

原因是在这种非内置类型的使用的时候,传入的是"9",实际上会调用string的默认构造函数string("9")进行一次转换,而类类型的转换只允许进行一次。int这样的内置类型则没有所谓的默认构造转换的操作。

上面的书写我们也可以合起来写成a_instance.print(string("9")) 

  作者:xTechnet
  出处:http://xTechnet.cnblogs.com/
  能力不济,但是乐于分享且没羞没臊,如有错误,请大力留言鄙视
  本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,好歹在文章页面明显位置给出原文连接,否则保留鄙视你的权利。

0 0