接第三章:C++编程宝典
2.8. 内联函数(inline function)
2.8.1. 内联
c 语言中有宏函数的概念。宏函数的特点是内嵌到调用代码中去,避免了函数调用的开销。但是由于宏函数的处理发生在预处理阶段,缺失了语法检测和有可能带来的语意差错。
2.8.2. 语法
C++提供了 inline 关键字,实现了真正的内嵌。
宏函数 VS inline 函数
#include
#include
using namespace std;
#if 0
优点:内嵌代码,辟免压栈与出栈的开销
缺点: 代码替换,易使生成代码体积变大,易产生逻辑错误。
#endif
#define SQR(x) ((x)*(x))
#if 0
优点:高度抽象,避免重复开发
缺点: 压栈与出栈,带来开销
#endif
inline int sqr(int x)
{
return x*x;
}
#endif
int main()
{
int i=0;
while(i<5)
{
// printf("%dn",SQR(i++));
printf("%dn",sqr(i++));
}
return 0;
}
2.8.3. 评价
优点:避免调用时的额外开销(入栈与出栈操作)
代价:由于内联函数的函数体在代码段中会出现多个“副本”,因此会增加代码段的空间。
本质:以牺牲代码段空间为代价,提高程序的运行时间的效率。
适用场景:函数体很“小”,且被“频繁”调用
2.9. 类型强转(type cast)
类型转换有 c 风格的,当然还有 c++风格的。c 风格的转换的格式很简单(TYPE)EXPRESSION,但是 c 风格的类型转换有不少的缺点,有的时候用 c 风格的转换是不合适的,因为它可以在任意类型之间转换,比如你可以把一个指向 const 对象的指针转换成指向非 const 对象的指针,把一个指向基类对象的指针转换成指向一个派生类对象的指针,这两种转换之间的差别是巨大的,但是传统的 c 语言风格的类型转换没有区分这些。还有一个缺点就是,c 风格的转换不容易查找,他由一个括号加上一个标识符组成,而这样的东西在c++程序里一大堆。所以 c++为了克服这些缺点,引进了 4 新的的类型转换操作符。
2.9.1. 静态类型转换:
语法格式:
static_cast<目标类型> (标识符)
所谓的静态,即在编译期内即可决定其类型的转换,用的也是最多的一种。
int a = 10;
int b = 3;
cout<<static_cast(a)/b<<endl;
return 0;
2.9.3. (脱)常量类型转换:
语法格式:
const_cast<目标类型> (标识符) //目标类类型只能是指针或引用。
#include
using namespace std;
struct A
{
int data;
};
int main(void)
{
const A a = {200};
// A a1 = const_cast(a);
// a1.data = 300;
A &a = const_cast<A&>(a);
a2.data = 300;
cout<<a.data<<a2.data<<endl;
A *a3 = const_cast<A*>(&a);
a3->data = 400;
cout<<a.data<data<<endl;
const int x = 3;
int &x1 = const_cast<int&>(x);
x1 = 300;
cout<<x<<x1<<endl;
int *x2 = const_cast<int*>(&x);
*x2 = 400;
cout<<x<<*x2<<endl;
return 0;
}
结论:
可以改变 const 自定义类的成员变量,但是对于内置数据类型,却表现未定义行为.
Depending on the type of the referenced object, a write operation through the resulting pointer, reference, or pointer to data member might produce undefined behavior.
应用场景:
#include
using namespace std;
void func(int & ref) //别人己经写好的程序或类库
{
cout<<ref<<endl;
}
int main(void)
{
const int m = 4444;
func(const_cast<int&>(m));
return 0;
}
const 常变量(补充):
C++中 const 定义的变量称为常变量。变量的形式,常量的作用,用作常量,常用于
取代#define 宏常量。
#include
using namespace std;
int main()
{
const int a = 200;
int b = 300;
int c = a +b
return 0;
}
2.9.4. 重解释类型转换:
语法格式:
reinterpret_cast<目标类型> (标识符)
interpret 是解释的意思,reinterpret 即为重新解释,此标识符的意思即为数据的二进制
形式重新解释,但是不改变其值。
#include
using namespace std;
int main()
{
int a[5] = {1,2,3,4,5};
// cout<<*((int)a +1)<<endl;
printf("%xn",*((int*)((int)a +1)));
cout<<*(reinterpret_cast<int*>(reinterpret_cast(a)+1))<<endl;
return 0;
}
2.10.命名空间(namespace scope)
2.10.1. 为什么要引入namespace
命名空间为了大型项目开发,而引入的一种避免命名冲突的一种机制。比如说,在一个大型项目中,要用到多家软件开发商提供的类库。在事先没有约定的情况下,两套类库可能在存在同名的函数或是全局变量而产生冲。项目越大,用到的类库越多,开发人员越多,这种冲突就会越明显。
2.10.2. 默认NameSpace(global &&function)
global scope 是一个程序中最大的 scope。也是引起命名冲突的根源。C 语言没有从语言层面提供这种机制来解决。也算是 C 语言的硬伤了。global scope 是无名的命名空间。
//c 语言中如何访问被局部变量覆盖的全局变量
int val = 200;
int main()
{
int *p = &val;
int val = 100;
printf("func val = %dn",val);
printf("global val = %dn",*p);
return 0;
}
#include
#include
using namespace std;
int val = 200;
void func()
{
return ;
}
int main()
{
int val = 100;
cout<<"func val = "<<val<<endl;
cout<<"global val = "<<::val<<endl;
::func(); //因为不能在函数内定义函数。所以前而的::没有意义。
return 0;
}
2.10.3. 语法规则
NameSpace 是对全局区域的再次划分。
声明及 namespace
namespace NAMESPACE
{
全局变量 int a;
数据类型 struct Stu{};
函数 void func();
}
使用方法
1.直接指定 命名空间: NameSpace::a = 5;
2.使用 using+命名空间+空间元素:using NameSpace::a; a = 2000;
3.使用 using +namespace+命名空间:
#include
using namespace std;
namespace MySpace
{
int val = 5;
int x,y,z;
}
int main()
{
// MySpace::val = 200;
// cout<<MySpace::val;
// using MySpace::x;
// using MySpace::y;
// x = 100;
// y = 200;
// cout<<x<<y<<endl;
using namespace MySpace;
val = 1;
x = 2;
y = 3;
z = 4;
cout<<val<<x<<y<<z<<endl;
return 0;
}
类比 std::cout using std::cout using namespact std;
无可辟免的冲突
#include
using namespace std;
namespace MySpace
{
int x = 1;
int y = 2;
}
namespace Other {
int x = 3;
int y = 4;
}
int main()
{
{
using namespace MySpace;
cout<<x<<y<<endl;
}
{
using namespace Other;
cout<<x<<y<<endl;
}
{
MySpace::x = 100;
Other::y = 200;
cout<<MySpace::x<<Other::y<<endl;
}
return 0;
}
支持嵌套
#include
using namespace std;
namespace MySpac
{
int x = 1;
int y = 2;
namespace Other {
int m = 3;
int n = 4;
}
}
int main()
{
using namespace MySpace::Other;
cout<<m<<n<<endl;
return 0;
}
协作开发
a.h
#ifndef A_H
#define A_H
namespace XX {
class A
{
public:
A();
~A();
};
}
#endif // A_H
a.cpp
#include "a.h"
using namespace XXX
{
A::A()
{
}
A::~A()
{
}
}
b.h
#ifndef B_H
#define B_H
namespace XX
{
class B
{
public:
B();
~B();
};
}
#endif // B_H
b.cpp
#include "b.h"
namespace XX {
B::B()
{
}
B::~B()
{
}
}
main.cpp
#include
#include "a.h"
#include "b.h"
using namespace std;
using namespace XX;
int main()
{
A a;
B b;
return 0;
}
2.11.系统 string 类
除了使用字符数组来处理字符串以外,c++引入了字符串类型。可以定义字符串变量。
2.11.1. 定义及初始化
int main()
{
string str;
str = "china";
string str2 = " is great ";
string str3 = str2;
cout<<str<<str2<<endl<<str3<<endl;
return 0;
}
2.11.2. 类型大小
cout<<"sizeof(string) = "<<sizeof(string)<<endl; cout<<"sizeof(str) = "<<sizeof(str)<<endl;
2.11.3. 运算
赋值
string str3 = str2;
加法
string combine = str + str2; cout<<combine<<endl;
关系
string s1 = "abcdeg";
string s2 = "12345";
if(s1>s2)
cout<<"s1>s2"<<endl;
else
cout<<"s1<s2"<<endl;
string s3 = s1-s2;
cout<<s3<<endl;
2.11.4. string 类型数组
string sArray[10] = {
"0",
"1",
"22",
"333",
"4444",
"55555",
"666666",
"7777777",
"88888888",
"999999999",
};
for(int i=0; i<10; i++)
{
cout<<sArray[i]<<endl;
}
string 数组是高效的,如果用二维数组来存入字符串数组的话,则容易浪费空间,此时列数是由最长的字符串决定。如果用二级指针申请堆空间,依据大小申请相应的空间,虽然解决了内存浪费的问题,但是操作麻烦。用string 数组存储,字符串数组的话,效率即高又灵活。
2.12.C++之父给 C 程序员的建议
1、在 C++中几乎不需要用宏,用 const 或 enum 定义明显的常量,用 inline 避免函数调用的额外开销,用模板去刻画一族函数或类型,用 namespace 去避免命名冲突。
2、不要在你需要变量之前去声明,以保证你能立即对它进行初始化。
3、不要用 malloc,new 运算会做的更好
4、避免使用 void*、指针算术、联合和强制,大多数情况下,强制都是设计错误的指示器。
5、尽量少用数组和 C 风格的字符串,标准库中的 string 和 vector 可以简化程序
6、更加重要的是,试着将程序考虑为一组由类和对象表示的相互作用的概念,而不是一堆数据结构和一些可以拨弄的二进制。
2.13.练习
2.13.1. 练习1用 cout 的格式控制,打一个时钟。
2.13.2. 练习2读字符串 char buf[100] = “xxxx:yyyy:zzzz:aaaa:bbb”.按:进行分解到,string 数组

C++封装在下一章继续讲解










[…] […]