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[];//注意数组容量,调试的时候又想起了最近看到的关于“烫烫烫烫”的冷笑话
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[];
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 = ;//错误,只读根本不能修改
c = ;//没有初始化
}
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 = ;
}; 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 = ;
class Scorp
{
public:
void print(int param)
{
cout << param << endl;//形参作用域
cout << Scorp::param << endl;//类中作用域,同this->param
cout << ::param << endl;//类外作用域
}
private:
int param = ;
}; int main(array<System::String ^> ^args)
{
Console::WriteLine(L"Hello World"); Scorp scorp = Scorp();
scorp.print();//【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();
a_instance.print();
}

输出结果是: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"))

C++基础细节2的更多相关文章

  1. js基础细节

    js细节 1.所有的全局变量都是window的属性. 语句 var a=1; 等价于 window.a=1; 用 "变量名称" in window 来验证全局变量是否声明. 2.所 ...

  2. 前端开发概述+JS基础细节知识点

    一 前端开发概述 html页面:html css javascript 拿到UI设计图纸:切图-->html+css静态布局-->用JS写一写动态效果-->ajax和后台进行交互,把 ...

  3. jQuery基础细节

    使用CDN版的jQuery         用托管在公共CDN(Content Delivery Network,内容分发网络)上的jQuery库,CDN是一种内容分发网络,当用户请求其数据时,CDN ...

  4. day8 java基础细节回顾

    java之父——James Gosling java吉祥物——duke 编译: 源文件(.java文件)-->java编译器==>.class文件 运行:类装载器-->字节码校验器- ...

  5. 你不知道的 JavaScript 基础细节

    语法部分 type 属性: 默认的 type 就是 javascript, 所以不必显式指定 type 为 javascript javascript 不强制在每个语句结尾加 “:” , javasc ...

  6. 日常笔记5C/C++快速入门一些基础细节

    一.变量数据类型 int大致范围:-210^9~210^9 long long大致范围:-910^18~910^18,对于长整型来说,如果赋值大于2^31-1的初值,就需要在初值后面加上LL,否则会编 ...

  7. Spring MVC基础了解

    参考网址:https://www.yiibai.com/spring_mvc/springmvc_overview.html Spring框架相关 Spring Security 一个灵活强大的身份验 ...

  8. GJM :多人在线游戏的设计思路

    感谢您的阅读.喜欢的.有用的就请大哥大嫂们高抬贵手"推荐一下"吧!你的精神支持是博主强大的写作动力以及转载收藏动力.欢迎转载! 版权声明:本文原创发表于 [请点击连接前往] ,未经 ...

  9. python 实现web框架simfish

    python 实现web框架simfish 本文主要记录本人利用python实现web框架simfish的过程.源码github地址:simfish WSGI HTTP Server wsgi模块提供 ...

随机推荐

  1. Android-经常涉及到的权限

    Android中配置权限的方法: 在AndroidMainFest.xml中加上以下代码 Android中一些经常涉及到的权限: 添加WiFi以及访问网络的权限: <uses-permissio ...

  2. ubuntu 的runlevel设定

    修改ubuntu的启动级别 runlevel ----------------------------------------------------------------------------- ...

  3. (转)Window 上安装Node.js

    window上安装nodejs非常的简单,next,next就行了,环境变量都是自动配置,不明白为毛java不这样 Window 上安装Node.js http://www.runoob.com/no ...

  4. rails执行sidekiq任务的时候报错“can't connect to local mysql server through socket '/var/run/mysqld/mysqld.sock'”

    rails执行sidekiq任务的时候报错“can't connect to local mysql server through socket '/var/run/mysqld/mysqld.soc ...

  5. zookeeper单节点安装

    1.安装jdk 2.安装解压zookeeper 先创建文件夹 解压zookeeper压缩包 3.  创建配置文件zoo.cfg 4.运行测试

  6. 如何安装MySQL软件

    1 双击EXE进行安装,在"Developer Components(开发者部分)"上左键单击,选择"This feature, and all subfeatures, ...

  7. 用开源NGINX-RTMP-MODULE搭建FLASH直播环境

    用开源nginx-rtmp-module搭建flash直播环境 1.将nginx和nginx-rtmp-module的源码包解压PS:nginx-rtmp-module网址https://github ...

  8. 赵雅智_service生命周期

    Android中的服务和windows中的服务是类似的东西,服务一般没实用户操作界面.它执行于系统中不easy被用户发觉,能够使用它开发如监控之类的程序. 服务的开发步骤 第一步:继承Service类 ...

  9. BEGINNING SHAREPOINT&#174; 2013 DEVELOPMENT 第2章节--SharePoint 2013 App 模型概览 总结

    BEGINNING SHAREPOINT® 2013 DEVELOPMENT 第2章节--SharePoint 2013 App 模型概览 总结         SharePoint Apps这三个新 ...

  10. log4j使用示例

    ### set log levels ### log4j.rootLogger = INFO , D #INFO , C , D , E ### console ### #log4j.appender ...