接第三章: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++封装在下一章继续讲解
[…] […]