0.目录

1.指针的判别

2.构造中的异常

3.令人迷惑的写法

4.小结

1.指针的判别

面试问题:

编写程序判断一个变量是不是指针。

指针的判别:

拾遗:

  • C++中仍然支持C语言中的可变参数函数
  • C++编译器的匹配调用优先级
    1. 重载函数
    2. 函数模板
    3. 变参函数

示例1——匹配调用优先级:

#include <iostream>

using namespace std;

void test(int i) // 1.重载函数
{
cout << "void test(int i)" << endl;
} template
<typename T>
void test(T i) // 2.函数模板
{
cout << "void test(T i)" << endl;
} void test(...) // 3.变参函数
{
cout << "void test(...)" << endl;
} int main(int argc, char *argv[])
{
int i = 0; test(i); return 0;
}

运行结果为:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out
void test(int i)

示例2——匹配调用优先级:

#include <iostream>

using namespace std;

template
<typename T>
void test(T i) // 2.函数模板
{
cout << "void test(T i)" << endl;
} void test(...) // 3.变参函数
{
cout << "void test(...)" << endl;
} int main(int argc, char *argv[])
{
int i = 0; test(i); return 0;
}

运行结果为:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out
void test(T i)

思路:

  • 将变量分为两类:指针 vs 非指针
  • 编写函数:
    1. 指针变量调用时返回true
    2. 非指针变量调用时返回false

函数模板与变参函数的化学反应:

示例——指针判断:

#include <iostream>

using namespace std;

class Test
{
public:
Test() { }
virtual ~Test() { }
}; template
<typename T>
bool IsPtr(T* v) // match pointer
{
return true;
} bool IsPtr(...) // match non-pointer
{
return false;
} int main(int argc, char *argv[])
{
int i = 0;
int* p = &i; cout << "p is a pointer: " << IsPtr(p) << endl; // true
cout << "i is a pointer: " << IsPtr(i) << endl; // false Test t;
Test* pt = &t; cout << "pt is a pointer: " << IsPtr(pt) << endl; // true
cout << "t is a pointer: " << IsPtr(t) << endl; // false return 0;
}

运行结果为:

[root@bogon Desktop]# g++ test.cpp
test.cpp: In function ‘int main(int, char**)’:
test.cpp:36: warning: cannot pass objects of non-POD type ‘class Test’ through ‘...’
[root@bogon Desktop]# ./a.out
p is a pointer: 1
i is a pointer: 0
pt is a pointer: 1
非法指令

(变参函数是C语言中的东西,根本不知道对象是什么,于是会报错。)

存在的缺陷:

  • 变参函数无法解析对象参数,可能造成程序崩溃!!

进一步的挑战:

  • 如何让编译器精确匹配函数,但不进行实际的调用?

示例——指针判断优化:

#include <iostream>

using namespace std;

class Test
{
public:
Test() { }
virtual ~Test() { }
}; template
<typename T>
char IsPtr(T* v) // match pointer
{
return 'd';
} int IsPtr(...) // match non-pointer
{
return 0;
} #define ISPTR(p) (sizeof(IsPtr(p)) == sizeof(char)) int main(int argc, char *argv[])
{
int i = 0;
int* p = &i; cout << "p is a pointer: " << ISPTR(p) << endl; // true
cout << "i is a pointer: " << ISPTR(i) << endl; // false Test t;
Test* pt = &t; cout << "pt is a pointer: " << ISPTR(pt) << endl; // true
cout << "t is a pointer: " << ISPTR(t) << endl; // false return 0;
}

运行结果为:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out
p is a pointer: 1
i is a pointer: 0
pt is a pointer: 1
t is a pointer: 0

(只匹配,不运行,就不会报错。)

2.构造中的异常

2.1 如果构造函数中抛出异常会发生什么?

面试问题:

如果构造函数中抛出异常会发生什么情况?

构造函数中抛出异常:

  • 构造过程立即停止
  • 当前对象无法生成
  • 析构函数不会被调用
  • 对象所占用的空间立即收回

工程项目中的建议:

  • 不要在构造函数中抛出异常
  • 当构造函数可能产生异常时,使用二阶构造模式

示例——构造中的异常:

#include <iostream>

using namespace std;

class Test
{
public:
Test()
{
cout << "Test()" << endl; throw 0;
}
virtual ~Test()
{
cout << "~Test()" << endl;
}
}; int main(int argc, char *argv[])
{
Test* p = reinterpret_cast<Test*>(1); try
{
p = new Test();
}
catch(...)
{
cout << "Exception..." << endl;
} cout << "p = " << p << endl; return 0;
}

运行结果为:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out
Test()
Exception...
p = 0x1

p指针并没有被赋值。(对象构造抛出异常,连空指针都不会返回,也就是说不会发生内存泄漏。)

2.2 如果析构函数中抛出异常会发生什么?

析构中的异常:

  • 避免在析构函数中抛出异常!!
  • 析构函数的异常将导致:
    1. 对象所使用的资源无法完全释放。

3.令人迷惑的写法

3.1 模板中的二义性

下面的程序想要表达什么意思?

历史上的原因:

  • 早期的C++直接复用class关键字来定义模板
  • 但是泛型编程针对的不只是类类型
  • class关键字的复用使得代码出现二义性

typename诞生的直接诱因:

  • 自定义类类型内部的嵌套类型
  • 不同类中的同一个标识符可能导致二义性
  • 编译器无法辨识标识符究竟是什么

示例1——能编译过的普通情况:

#include <iostream>

using namespace std;

int a = 0;

class Test_1
{
public:
static const int TS = 1;
}; class Test_2
{
public:
struct TS
{
int value;
};
}; template
< class T >
void test_class()
{
T::TS * a; // 1. 通过泛指类型 T 内部的数据类型 TS 定义指针变量 a (推荐的解读方式)
// 2. 使用泛指类型 T 内部的静态成员变量 TS 与全局变量 a 进行乘法操作
} int main(int argc, char *argv[])
{
test_class<Test_1>();
// test_class<Test_2>(); return 0;
}

示例2——模板中的二义性:

#include <iostream>

using namespace std;

int a = 0;

class Test_1
{
public:
static const int TS = 1;
}; class Test_2
{
public:
struct TS
{
int value;
};
}; template
< class T >
void test_class()
{
T::TS * a; // 1. 通过泛指类型 T 内部的数据类型 TS 定义指针变量 a (推荐的解读方式)
// 2. 使用泛指类型 T 内部的静态成员变量 TS 与全局变量 a 进行乘法操作
} int main(int argc, char *argv[])
{
// test_class<Test_1>();
test_class<Test_2>(); return 0;
}

运行结果为:

[root@bogon Desktop]# g++ test.cpp
test.cpp: In function ‘void test_class() [with T = Test_2]’:
test.cpp:33: instantiated from here
test.cpp:26: error: dependent-name ‘T::TS’ is parsed as a non-type, but instantiation yields a type
test.cpp:26: note: say ‘typename T::TS’ if a type is meant

示例3——使用typename解决模板中的二义性:

#include <iostream>

using namespace std;

int a = 0;

class Test_1
{
public:
static const int TS = 1;
}; class Test_2
{
public:
struct TS
{
int value;
};
}; template
< class T >
void test_class()
{
typename T::TS * a; // 1. 通过泛指类型 T 内部的数据类型 TS 定义指针变量 a (推荐的解读方式)
// 2. 使用泛指类型 T 内部的静态成员变量 TS 与全局变量 a 进行乘法操作
} int main(int argc, char *argv[])
{
// test_class<Test_1>();
test_class<Test_2>(); return 0;
}

typename的作用:

  1. 在模板定义中声明泛指类型
  2. 明确告诉编译器其后的标识符为类型

3.2 函数异常声明

下面的程序想要表达什么意思?

  • try ... catch用于分隔正常功能代码与异常处理代码
  • try ... catch可以直接将函数实现分隔为2部分
  • 函数声明和定义时可以直接指定可能抛出的异常类型
  • 异常声明成为函数的一部分可以提高代码可读性

函数异常声明的注意事项:

  • 函数异常声明是一种与编译器之间的契约
  • 函数声明异常后就只能抛出声明的异常
    1. 抛出其它异常将导致程序运行终止
    2. 可以直接通过异常声明定义无异常函数

示例——新的异常写法:

#include <iostream>

using namespace std;

int func(int i, int j) throw(int, char)
{
if( (0 < j) && (j < 10) )
{
return (i + j);
}
else
{
throw '0';
}
} void test(int i) try
{
cout << "func(i, i) = " << func(i, i) << endl;
}
catch(int i)
{
cout << "Exception: " << i << endl;
}
catch(...)
{
cout << "Exception..." << endl;
} int main(int argc, char *argv[])
{
test(5); test(10); return 0;
}

运行结果为:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out
func(i, i) = 10
Exception...

4.小结

  • C++中依然支持变参函数
  • 变参函数无法很好的处理对象参数
  • 利用函数模板和变参函数能够判断指针变量
  • 构造函数和析构函数中不要抛出异常
  • class可以用来在模板中定义泛指类型(不推荐)
  • typename是可以消除模板中的二义性
  • try...catch 可以将函数体分成2部分
  • 异常声明能够提供程序的可读性

C++解析(30):关于指针判别、构造异常和模板二义性的疑问的更多相关文章

  1. 异常处理与MiniDump详解(2) 智能指针与C++异常

    write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie 讨论新闻组及文件 一.   综述 <异常处理与MiniDump详解(1) C++异常>稍 ...

  2. 数据结构图文解析之:栈的简介及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  3. 数据结构图文解析之:队列详解与C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  4. Flask框架(二)—— 反向解析、配置信息、路由系统、模板、请求响应、闪现、session

    Flask框架(二)—— 反向解析.配置信息.路由系统.模板.请求响应.闪现.session 目录 反向解析.配置信息.路由系统.模板.请求响应.闪现.session 一.反向解析 1.什么是反向解析 ...

  5. Delphi之通过代码示例学习XML解析、StringReplace的用法(异常控制 good)

    *Delphi之通过代码示例学习XML解析.StringReplace的用法 这个程序可以用于解析任何合法的XML字符串. 首先是看一下程序的运行效果: 以解析这样一个XML的字符串为例: <? ...

  6. HashMap 源码解析(一)之使用、构造以及计算容量

    目录 简介 集合和映射 HashMap 特点 使用 构造 相关属性 构造方法 tableSizeFor 函数 一般的算法(效率低, 不值得借鉴) tableSizeFor 函数算法 效率比较 tabl ...

  7. DRF框架(二)——解析模块(parsers)、异常模块(exception_handler)、响应模块(Response)、三大序列化组件介绍、Serializer组件(序列化与反序列化使用)

    解析模块 为什么要配置解析模块 1)drf给我们提供了多种解析数据包方式的解析类 form-data/urlencoded/json 2)我们可以通过配置来控制前台提交的哪些格式的数据后台在解析,哪些 ...

  8. C++解析(27):数组、智能指针与单例类模板

    0.目录 1.数组类模板 1.1 类模板高效率求和 1.2 数组类模板 1.3 堆数组类模板 2.智能指针类模板 2.1 使用智能指针 2.2 智能指针类模板 3.单例类模板 3.1 实现单例模式 3 ...

  9. C++学习笔记30,指针的引用(2)

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/guang_jing/article/details/32910093 能够创建不论什么类型的引用,包 ...

随机推荐

  1. SPOJ 694&&SPOJ705: Distinct Substrings

    DISUBSTR - Distinct Substrings 链接 题意: 询问有多少不同的子串. 思路: 后缀数组或者SAM. 首先求出后缀数组,然后从对于一个后缀,它有n-sa[i]-1个前缀,其 ...

  2. Mysql:查询当天、今天、本周、上周、本月、上月、本季度、本年的数据

    1. 今天 select * from 表名 WHERE TO_DAYS(时间字段名) = TO_DAYS(NOW()); 2. 昨天 3. 本周 SELECT * FROM 表名 WHERE YEA ...

  3. 记录使用jQuery和Python抓取采集数据的一个实例

    从现成的网站上抓取汽车品牌,型号,车系的数据库记录. 先看成果,大概4w条车款记录 一共建了四张表,分别存储品牌,车系,车型和车款 大概过程: 使用jQuery获取页面中呈现的大批内容 能通过页面一次 ...

  4. 关于网易云验证码V1.0版本的服务介绍

    服务介绍 易盾验证码是一个用于区分人和机器的通用验证码组件.传统的字符型验证码由于存在破解率高,用户体验不友好等问题,已不适用于现今的互联网环境.易盾验证码抛弃了传统字符型验证码展示-填写字符-比对答 ...

  5. PHP基础知识试题

    转载于:http://www.php.cn/toutiao-415599.html 1.PHP中传值与传引用的区别,什么时候传值,什么时候传引用? 按值传递:函数范围内对值任何改变在函数外部都会被忽略 ...

  6. Django的简介

    一.MTV模型 Django的MTV模式: Model(模型):和数据库相关的.负责业务对象与数据库的对象(ORM) Template(,模板):放所有的HTML文件 模板语法:目的是将变量(数据库内 ...

  7. 添加jQuery方法解析url查询部分

    Web前端不同页面间传值可以使用 cookies.localStorage 和 sessionStorage 等本地存储. 但是,今天我们尝试使用 url 查询,假设我们要传递字符串 str 到 mo ...

  8. java计算两个日期之间的天数,排除节假日和周末

    如题所说,计算两个日期之前的天数,排除节假日和周末.这里天数的类型为double,因为该功能实现的是请假天数的计算,有请一上午假的为0.5天. 不够很坑的是每个日期都要查询数据库,感觉很浪费时间. 原 ...

  9. OpenLDAP搭建部署

    安装环境: linu系统:      centos7.2版本 OenLDAP:/openldap-2.4.44 下载地址:ftp://ftp.openldap.org/pub/OpenLDAP/ope ...

  10. VPS挂机赚美刀详细介绍–Alexamaster操作流程

    跟 vps 主机打交道时间长了,手里也渐渐积累了些闲置的 vps.让它们这么闲着吧,感觉有些浪费资源:用起来吧,暂时又没有好的项目.一直听说通过 vps挂机可以赚回主机成本,甚至可以盈利.正好这两天有 ...