C++深度剖析(上)

C++深度剖析(上)

所有代码均在Ubuntu Linux 16.04 GNU 编译器 gcc 5.6 g++5.6 下编译通过

第 1 课 学习 C++的意义

UNIX 操作系统诞生之初是直接用汇编语言写成的
随着 UNIX 的发展,汇编语言的开发效率称为一个瓶颈
如何提高 UNIX 的开发效率

C 语言是实践的过程中逐步完善起来的
没有深思熟虑的设计过程
残留量过多低级语言的特征
C 语言的目标是高效
最终程序执行效率的高效

面向过程程序设计:数据结构+算法
主要解决科学计算问题,用户需求简单而固定
特点:
分析解决问题所需要的步骤
利用函数实现各个步骤
依次调用函数解决问题
问题:
软件可重用性差
软件可维护性差

面向对象程序设计:由现实世界建立软件模型
将现实中的事物直接映射到程序中,可直接满足用户需求
特点:
直接分析用户需求中涉及的各个实体
在代码中描述现实世界的实体
在代码中关联各个实体协同工作解决问题
优势:
构建的软件能够适应用户需求的不断变化
直接利用面向过程方法的优势而避开其劣势

当面向过程方法论暴露越来越多的缺陷的时候,业界开始考虑在工程中引入面向对象的设计
方法,而第一个需要解决的问题就是:高效的面向对象语言,并且能够兼容已经存在的代码。

C 语言和 C++并不是对立竞争关系
C++是 C 语言的加强,是一种更好的 C 语言
C++是 C 语言为基础的,并且完全兼容 C 语言的特性

学习 C++并不会影响原有 C 语言知识,相反,学习 C++可以让我们学到更多的软件设计方法。

系统一般是由多种语言写成的
系统架构的目标是拥抱用户需求的变化
系统拥有较好的移植性
系统能够方便的部署和更新
操作系统的修改和驱动程序的开发在现代软件产品中占据的比重越来越小。

小结:
所有软件系统都是为了满足用户需求而开发的
对于一个开发团队而言,在短期内开发出用户满意的软件系统是其核心竞争力的体现
对于一个开发者而言,能够胜任系统中任意一个模块的开发是其核心价值的体现
对于一个架构师而言,掌握各种语言的优势并合理运用到系统中,由此简化系统的开发,
是其核心价值的体现

第 2 课 C 到 C++的升级

C++继承了所有的 C 特性
C++在 C 的基础上提供了更多的语法和特性
C++的设计目标是运行效率与开发效率的统一

C++更强调语言的实用性
所有的变量都可以在需要使用时再定义
int c=0;
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
c+=i*j;
}
}
对此:
C 语言中的变量都必须在作用域开始的位置定义

register 关键字请求编译器将局部变量存储于寄存器中
在 c++中依然支持 register 关键字
c++编译器有自己的优化方式
c 语言中无法获取 register 变量的地址
c++中可以取得 register 变量的地址

C++中 register 关键字
c++编译器发现程序中需要取 register 变量的地址,register 对变量的声明变得无效。
早期 c 语言编译器不会对代码进行优化,因此 register 变量是一个很好的补充。

在 c 语言中,重复定义多个同名的全局变量时合法的
在 c++中,不允许定义多个同名的全局变量
c 语言中多个同名的全局变量最终会被链接到全局数据区的同一个地址空间上。
c++直接拒绝这种而定义的做法

例 1 c 到 c++的升级一

#include <stdio.h>

int g_v;
//int g_v;

int main(int argc, char *argv[])
{
    printf("Begin...\n");

    int c = 0;

    for(int i=1; i<=3; i++)
    {
        for(int j=1; j<=3; j++)
        {
            c += i * j;
        }
    }

    printf("c = %d\n", c);

    register int a = 0;

    printf("&a = %p\n", &a);

    printf("End...\n");

    return 0;
}

struct 关键字的加强
c 语言中 struct 定义了一组变量的集合
c 语言中 struct 定义的标识符并不是一种新的类型
c++中的 struct 用于定义一个全新的类型

typedef struct _tag_student Student;
struct _tag_student
{
const char* name;
int age;
};

struct Student
{
const char* name;
int age;
};

面试小问题:
int f()与 int f(void)有区别吗?
如果有区别是什么?

C++中所有的标识符都必须显示的声明类型
C 语言中的默认类型在 c++中是不合法的
f(i)
{
printf(“i=%d\n”,i);
}

g()
{
return 5;
}
问题:
1、函数 f 的返回值和参数分别是什么类型的?
2、函数 g 可以接受多少个参数?
答:在 c 语言中
int f()表示返回值为 int,接受任意参数的函数
f(void)表示返回值为 int 的无参函数
在 C++中
int f()和 int f(void)具有相同的意思
表示返回值为 int 的无参函数

例 2 c 到 c++的升级二

#include <stdio.h>
struct Student
{
    const char* name;
    int age;
};

f(i)
{
    printf("i = %d\n", i);
}

g()
{
    return 5;
}

int main(int argc, char *argv[])
{
    Student s1 = {"Delphi", 30};
    Student s2 = {"Tang", 30};

    f(10);

    printf("g() = %d\n", g(1,2,3,4,5));

    return 0;
}

小结:
C++更强调实用性,可以在任意的地方声明变量
C++中的 register 只是一个兼容的作用
C++编译器能够更好的进行优化
C++中的任意标识符都必须显示的指明类型

第 3 课 进化后的 const 分析

const 修饰的变量时只读的,本质还是变量
const 修饰的局部变量在栈上分配空间
const 修饰的全局变量在只读存储区分配空间
const 只在编译器有用,在运行期无用
const 修饰的变量不是真的常量,它只是告诉编译器该变量不能出现在赋值符号的左边

C 语言中的 const 使得变量具有只读性
const 将具有全局生命周期的变量存储于只读存储区
const 不能定义真正意义上的常量

例 1 c/c++中的 const

#include <stdio.h>

int main()
{
    const int c = 0;
    int* p = (int*)&c;

    printf("Begin...\n");

    *p = 5;

    printf("c = %d\n", c);

    printf("End...\n");

    return 0;
}

例 2 c/c++中的 const

#include <stdio.h>

int main()
{
    const int c = 0;
    int* p = (int*)&c;

    printf("Begin...\n");

    *p = 5;

    printf("c = %d\n", c);

    printf("End...\n");

    return 0;
}

C++在 C 的基础上对 const 进行了进化处理
当碰见 const 声明时在符号表中放入常量
编译过程中若发现使用常量则直接以符号表中的值替换
编译过程中若发现下述情况则给对应的常量分配存储空间
对 const 常量使用了 extern
对 const 常量使用&操作符
注意:c++编译器虽然可能为 const 常量分配空间,但不会使用其存储空间的值。

C 语言中的 const 变量
c 语言中的 const 变量是只读变量,会分配存储空间的
c++中的 const 常量
可能编配存储空间
当 const 常量为全局,并且需要在其他文件中使用
当使用&操作符对 const 常量取地址

c++中的 const 常量类似于宏定义
const int c=5; 约等于#define c 5
c++中的 const 常量在与宏定义不同

const 常量是由编译器处理
编译器对 const 常量进行类型检查和作用域检查
宏定义由预处理器处理,单纯的文本替换

例 3 const 与宏

#include <stdio.h>

void f()
{
    #define a 3
    const int b = 4;
}

void g()
{
    printf("a = %d\n", a);
    //printf("b = %d\n", b);
}

int main()
{
    const int A = 1;
    const int B = 2;
    int array[A + B] = {0};
    int i = 0;

    for(i=0; i<(A + B); i++)
    {
        printf("array[%d] = %d\n", i, array[i]);
    }

    f();
    g();

    return 0;
}

小结:
与 c 语言不同,c++中的 const 不是只读变量
c++中的 const 是一个真正意义上的常量
c++编译器可能会为 const 常量分配空间
c++完全兼容 c 语言中 const 常量的语法特性

第 4 课 布尔类型的引用

c++中的布尔类型
c++在 c 语言的基本类型系统之上增加了 bool
c++中的 bool 可取的值只有 true 和 false
理论上 bool 只占用一个字节
注意:
true 代表真值,编译器内部用 1 来表示
false 代表非真值,编译器内部用 0 来表示

bool 类型的值
bool 类型只有 true(非 0)和 false(0)两个值
c++编译器会将非 0 值转换为 true,0 值转换为 false
下面的代码输出什么?
bool b=0;
printf(“b=%d\n”,b);
b++;
printf(“b=%d\n”,b);
b=b-3;
printf(“b=%d\n”,b);

例 1 布尔类型的使用

#include <stdio.h>

int main(int argc, char *argv[])
{
    bool b = false;
    int a = b;

    printf("sizeof(b) = %d\n", sizeof(b));
    printf("b = %d, a = %d\n", b, a);

    b = 3;
    a = b;

    printf("b = %d, a = %d\n", b, a);

    b = -5;
    a = b;

    printf("b = %d, a = %d\n", b, a);

    a = 10;
    b = a;

    printf("a = %d, b = %d\n", a, b);

    a = 0;
    b = a;

    printf("a = %d, b = %d\n", a, b);

    return 0;
}

布尔类型是 c++中最基本的数据类型
可以定义 bool 类型的全局变量
可以定义 bool 类型的常量
可以定义 bool 类型的指针
可以定义 bool 类型的数组
。。。

C++对三目运算符进行了升级
C 语言中的三目运算符返回的是变量值
不能作为左值使用

c++对三目运算符进行了升级
下面的代码正确吗?
int a=1;
int b=2;
(a

#include <stdio.h>

int main(int argc, char *argv[])
{
    int a = 4;
    int& b = a;

    b = 5;

    printf("a = %d\n", a);
    printf("b = %d\n", b);
    printf("&a = %p\n", &a);
    printf("&b = %p\n", &b);

    return 0;
}

c++中的引用
c++对三目运算符做了什么?
当三目运算符的可能返回都是变量时,返回的是变量引用
当三目运算符的可能返回中有常量时,返回的是值
int a=1;
int b=2;
(a

第 5 课 引用的本质分析

引用作为变量别名而存在,因此在一些场合可以代替指针
引用相对于指针来说具有更好的可读性和实用性
void swap(int &a,int &b)
{
int t=a;
a=b;
b=t;
}
void swap(int *a,int *b)
{
int t=*a;
*a=*b;
*b=t;
}

const 引用
在 C++中可以声明 const 引用
const Type& name=var;
const 引用让变量拥有只读属性
int a=4;
cons tint& b=a;
int p=(int )&b;
b=5; //error,只读变量
*p=5; //ok,修改变量 a 的值

当使用常量对 const 引用进行初始化时,c++编译器会为常量值分配空间,并将引用名作为
这段空间的别名
cons tint& b=1; //ok
int p=(int )&b;
b=5; //error,只读变量
*p=5; //ok,修改变量 a 的值
结论:
使用常量对 const 引用初始化后将生成一个只读变量。

例 1 引用的特殊意义

#include <stdio.h>

void Example()
{
    printf("Example:\n");

    int a = 4;
    const int& b = a;
    int* p = (int*)&b;

    //b = 5;

    *p = 5;

    printf("a = %d\n", a);
    printf("b = %d\n", b);
}

void Demo()
{
    printf("Demo:\n");

    const int& c = 1;
    int* p = (int*)&c;

    //c = 5;

    *p = 5;

    printf("c = %d\n", c);
}

int main(int argc, char *argv[])
{
    Example();

    printf("\n");

    Demo();

    return 0;
}

引用有自己的存储空间吗?
struct TRef
{
char& r;
};
printf(“sizeof(TRef)=%d\n”,sizeof(TRef));

例 2 引用的思考

#include <stdio.h>

struct TRef
{
    char& r;
};

int main(int argc, char *argv[])
{ 
    char c = 'c';
    char& rc = c;
    TRef ref = { c };

    printf("sizeof(char&) = %d\n", sizeof(char&));
    printf("sizeof(rc) = %d\n", sizeof(rc));

    printf("sizeof(TRef) = %d\n", sizeof(TRef));
    printf("sizeof(ref.r) = %d\n", sizeof(ref.r));

    return 0;
}

引用在 c++中的内部实现是一个指针常量
type&name;
void f(int &a)
{
a=5;
}
等于
type *const name;
void f(int *const a)
{
*a=5;
}

注意:
1、c++编译器在编译过程中用指针常量作为引用的内部实现,因此引用所占用的空间大
小与指针相同。
2、从使用的角度,引用只是一个别名,c++为了实用性而隐藏了引用的存储空间的这一
细节。

例 3 引用的存储空间

#include <stdio.h>

struct TRef
{
    char* before;
    char& ref;
    char* after;
};

int main(int argc, char* argv[])
{
    char a = 'a';
    char& b = a;
    char c = 'c';

    TRef r = {&a, b, &c};

    printf("sizeof(r) = %d\n", sizeof(r));
    printf("sizeof(r.before) = %d\n", sizeof(r.before));
    printf("sizeof(r.after) = %d\n", sizeof(r.after));
    printf("&r.before = %p\n", &r.before);
    printf("&r.after = %p\n", &r.after);

    return 0;
}

C++中的引用指在大多数的情况下代替指针
功能性:可以满足多数需要使用指针的场合
安全性:可以避开由于指针操作不当而带来的内存错误
操作性:简单易用,又不失功能强大

例 4 函数返回引用

#include <stdio.h>

int& demo()
{
    int d = 0;

    printf("demo: d = %d\n", d);

    return d;
}

int& func()
{
    static int s = 0;

    printf("func: s = %d\n", s);

    return s;
}

int main(int argc, char* argv[])
{
    int& rd = demo();
    int& rs = func();

    printf("\n");
    printf("main: rd = %d\n", rd);
    printf("main: rs = %d\n", rs);
    printf("\n");

    rd = 10;
    rs = 11;

    demo();
    func();

    printf("\n");
    printf("main: rd = %d\n", rd);
    printf("main: rs = %d\n", rs);
    printf("\n");

    return 0;
}

小结:
引用作为变量别名而存在指在代替指针
const 引用可以使得变量具有只读属性
引用在编译器内部使用指针常量实现
引用的最终本质为指针
引用可以尽可能的避开内存错误

第 6 课 内联函数分析

C++中的 const 常量可以替代宏常数定义,如:
const int A=3; <====>#define A 3
C++中是否有解决方案替代宏代码片段呢?

C++中推介使用内联函数替代宏代码片段
C++中使用 inline 关键字声明内联函数
inline int func(int a,int b)
{
return a

#include <stdio.h>

#define FUNC(a, b) ((a) < (b) ? (a) : (b))

inline int func(int a, int b)
{
    return a < b ? a : b;
}

int main(int argc, char *argv[])
{
    int a = 1;
    int b = 3;
    int c = FUNC(++a, b);

    printf("a = %d\n", a);
    printf("b = %d\n", b);
    printf("c = %d\n", c);

    return 0;
}

内联函数具有普通函数的特征(参数检查,返回类型等)
函数的内联请求可能被编译器拒绝
函数被内联编译后,函数体直接扩展到调用的地方
宏代码片段由预处理器处理,进行简单的文本替换,没有任何编译过程,因此可能出现
副作用

现代 C++编译器能够进行编译优化,一些函数即使没有 inline 声明,也可能被内联编译。
一些现代 c++编译器提供了扩展语法,能够对函数进行强制内联,如:
g++:attribute((always_inline))属性
MSVC:_forceinline

例 2 内联函数深度示例

#include <stdio.h>

//__forceinline
//__attribute__((always_inline))
inline 
int add_inline(int n);

int main(int argc, char *argv[])
{
    int r = add_inline(10);

    printf(" r = %d\n", r);

    return 0;
}

inline int add_inline(int n)
{
    int ret = 0;

    for(int i=0; i<n; i++)
    {
        ret += i;
    }

    return ret;
}

注意事项
C++中的 inline 内联编译的限制:
不能存在任何形式的循环语句
不能存在过多的条件判断语句
函数体不能过于庞大
不能对函数进行取址操作
函数内联声明必须在函数语句之前

小结
C++中可以通过 inline 声明内联函数
编译器直接将内联函数扩展到函数调用的地方
inline 只是一种请求,编译器不一定允许这种请求
内联函数省去了函数调用时压栈,跳转和返回的开销

第 7 课 函数参数的扩展

C++中可以在函数声明时为参数提供一个默认值
当函数调用时没有提供参数的值时,则使用默认值
int mul(int x=0);
int main(int argc,char *argv[])
{
printf(“%d\n”,mul()); //mul(0)
return 0;
}

int mul(int x)
{
return x*x;
}

函数参数的默认值
参数的默认值必须在函数声明中指定
问题:
函数定义中是否可以出现参数的默认值?
当函数声明和定义中的参数默认值不同时会发生什么?
int mul(int x=0);
//…
int mul(int x=5)
{
return x*x;
}
r 的值是什么?
int r=mul();

例 1 默认参数值初探

#include <stdio.h>

int mul(int x = 0);

int main(int argc, char *argv[])
{
    printf("%d\n", mul());
    printf("%d\n", mul(-1));
    printf("%d\n", mul(2));

    return 0;
}

int mul(int x)
{
    return x * x;
}

函数参数默认参数的规则
参数的默认值必须从右向左提供
函数调用时使用了默认值,则后续参数必须使用默认值
int add(int x,int y=1,int z=2)
{
return x+y+z;
}
add(0); //x=0,y=1,z=2
add(2,3); //x=2,y=3,z=2
add(3,2,1); //x=3,y=2,z=1

例 2 默认参数的错误示例

#include <stdio.h>

int add(int x, int y = 0, int z = 0);

int main(int argc, char *argv[])
{
    printf("%d\n", add(1));
    printf("%d\n", add(1, 2));
    printf("%d\n", add(1, 2, 3));

    return 0;
}

int add(int x, int y, int z)
{
    return x + y + z;
}

函数占位参数
在 C++中可以为函数提供占位参数
占位参数只有参数类型声明,而没有参数名声明
一般情况下,在函数体内部无法使用占位参数

int func(int x,int)
{
return x;
}
func(1,2); //ok

函数占位参数的意义
占位参数与默认参数结合起来使用
兼容 C 语言程序中可能出现的不规范写法
下面两种声明方式等价吗?
void func(); void func(void);
c 语言不等价,c++等价

例 3 占位参数与默认参数值

#include <stdio.h>

int func(int x, int = 0);

int main(int argc, char *argv[])
{
    printf("%d\n", func(1));
    printf("%d\n", func(2, 3));

    return 0;
}

int func(int x, int)
{
    return x;
}

小结:
C++中支持函数参数的默认值
如果函数调用时没有提供参数值,则使用默认值
参数的默认值必须从右向左提供
函数调用时使用了默认值,则后续参数必须使用默认值
C++中支持占位参数,用于兼容 c 语言中的不规范写法

第 8 课 函数重载(上)

重载,同一个标识符在不同的上下文有不同的意义

函数重载
用同一个函数名定义不同的函数
当函数名和不同的参数搭配时函数的含义不同
int func(int x)
{
return x;
}
int func(int a,int b)
{
return a+b;
}
int func(const char* s)
{
return strlen(s);
}

例 1 函数重载初探

#include <stdio.h>
#include <string.h>

int func(int x)
{
    return x;
}

int func(int a, int b)
{
    return a + b;
}

int func(const char* s)
{
    return strlen(s);
}


int main(int argc, char *argv[])
{
    printf("%d\n", func(3));
    printf("%d\n", func(4, 5));
    printf("%d\n", func("D.T.Software"));

    return 0;
}

函数重载至少满足下面的一个条件:
参数个数不同
参数类型不同
参数顺序不同
int func(int a,const char *s)
{
return a;
}
int func(const char *s,int a)
{
return strlen(s);
}
上面两个函数可以构成重载函数吗?

当函数默认参数遇上函数重载会发生什么?
int func(int a,int b,int c=0)
{
return a*b*c;
}
int func(int a,int b)
{
return a+b;
}
int main()
{
int c=func(1,2);
return 0;
}

例 2 函数默认参数 VS 函数重载

#include <stdio.h>

int func(int a, int b, int c = 0)
{
    return a * b * c;
}

int func(int a, int b)
{
    return a + b;
}


int main(int argc, char *argv[])
{
    int c = func(1, 2);

    return 0;
}

编译器调用重载函数的准则
将所有同名函数作为候选者
尝试寻找可行的候选函数
精确匹配实参
通过默认参数能够匹配实参
通过默认类型转换匹配实参
匹配失败
最终寻找到的候选函数不唯一,则出现二定义,编译失败。
无法匹配所有候选者,函数未定义,编译失败。

函数重载的注意事项
重载函数在本质上是相互独立的不同函数
重载函数的函数类型不同
函数返回值不能作为函数重载的依据
函数重载由函数名和参数列表决定的

例 3 函数重载的本质

#include <stdio.h>

int add(int a, int b)  // int(int, int)
{
    return a + b;
}

int add(int a, int b, int c) // int(int, int, int)
{
    return a + b + c;
}

int main()
{
    printf("%p\n", (int(*)(int, int))add);
    printf("%p\n", (int(*)(int, int, int))add);

    return 0;
}

小结:
函数重载是 c++中引入的概念
函数重载用于模拟自然语言中的词汇搭配
函数重载使得 c++具有更丰富的语言表达能力
函数重载的本质为相互独立的不同函数
c++中通过函数名和函数参数确定函数调用

第 9 课 函数重载分析(下)

下面的函数指针将保存哪个函数的地址?
int func(int x)
{
return x;
}
int func(int a,int b)
{
return a+b;
}
int func(const char *s)
{
return strlen(s);
}

typedef int(*PFUNC)(int a)
int c=0;
PFUNC p=func;
c=p(1);

函数重载遇上函数指针
将重载函数名赋值给函数指针时
1、根据重载规则挑选与函数指针参数列表一致的候选者
2、严格匹配候选者的函数类型与函数指针的函数类型

例 1 函数重载 VS 函数指针

#include <stdio.h>
#include <string.h>

int func(int x)
{
    return x;
}

int func(int a, int b)
{
    return a + b;
}

int func(const char* s)
{
    return strlen(s);
}

typedef int(*PFUNC)(int a);


int main(int argc, char *argv[])
{
    int c = 0;

    PFUNC p = func;

    c = p(1);   

    printf("c = %d\n", c);

    return 0;
}

注意:
函数重载必然发生在同一个作用域中
编译器需要用参数列表或函数类型进行函数选择
无法直接通过函数名得到重载函数的入口地址

c++和 c 相互调用
实际工程中 C++和 C 代码相互调用是不可避免的
C++编译器能够兼容 C 语言的编译方式
C++编译器会优先使用 C++编译的方式
extern 关键字能强制让 C++编译器进行 C 方式编译
extern “C”
{
//do c-style compilation here
}

例 2 c++调用 c 函数

//add.c
#include "add.h"
int add(int a, int b)
{
    return a + b;
}
//add.h
int add(int a, int b);
//main.cpp
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif

#include "add.h"
#ifdef __cplusplus
}
#endif


int main()
{
    int c = add(1, 2);

    printf("c = %d\n", c);

    return 0;
}

问题:
如何保证一段 c 代码只会以 c 的方式被编译

解决方案
__cplusplus 是 c++编译器内置的标准宏定义
__cplusplus 的意义
确保 c 代码以统一的 c 方式被编译成目标文件

#ifdef __cplusplus
extern “C”{
#endif
//c-style compilation
#ifdef __cplusplus
}
#endif

注意事项
C++编译器不能以 C 的方式编译重载函数
编译方式决定函数名被编译后的目标名
C++编译方式将函数名和参数列表编译成目标名
C 编译方式只将函数名作为目标名进行编译

小结:
函数重载是 c++对 c 的一个重要升级
函数重载通过函数参数列表区分不同的同名函数
extern 关键字能够实现 c 和 c++的相互调用
编译方式决定符号表中的函数名的最终目标名

第 10 课 C++中的新成员

C++中的动态内存分配
C++中通过 new 关键字进行动态内存申请
C++中的动态内存申请是基于类型进行的
delete 关键字用于内存释放

变量申请:
type *point=new type;
//…
delete pointer;

数组申请:
type *pointer=new type[N];
//…
delete[] pointer;

例 1 c++中的动态内存分配

#include <stdio.h>

int main()
{
    int* p = new int;

    *p = 5;
    *p = *p + 10;

    printf("p = %p\n", p);
    printf("*p = %d\n", *p);

    delete p;

    p = new int[10];

    for(int i=0; i<10; i++)
    {
        p[i] = i + 1;

        printf("p[%d] = %d\n", i, p[i]);
    }

    delete[] p;

    return 0;
}

new 关键字与 malloc 函数区别?
nem 关键字是 C++的一部分
malloc 是由 C 库提供的函数
new 以具体类型为单位进行内存分配
malloc 以字节为单位进行内存分配
new 在申请单个类型变量时可进行初始化
malloc 不具备内存初始化的特性

new 关键字的初始化
int *pi=new int[1];
float *pf=new float(2.0f)
char *pc=new char(‘c’);
例 2 初始化动态内存

#include <stdio.h>

int main()
{
    int* pi = new int(1);
    // int* pa = new int[1];

    float* pf = new float(2.0f);
    char* pc = new char('c');

    printf("*pi = %d\n", *pi);
    printf("*pf = %f\n", *pf);
    printf("*pc = %c\n", *pc);

    delete pi;
    delete pf;
    delete pc;

    return 0;
}

c++中的命名空间
在 C 语言中只有一个全局作用域
C 语言中所有的全局标识符共享同一个作用域
标识符之间可能发生冲突
C++中提出了命名空间的概念
命名空间将全局作用域分成不同的部分
不同命名空间中的标识符可以同名而不会发生冲突
命名空间可以相互嵌套
全局作用域也叫默认命名空间

C++命名空间的定义
namespace Name
{
namespace Internal
{
//…
}
//…
}

C++命名空间的使用:
使用整个命名空间:using namespace name;
使用命名空间中的变量:using name::variable;
使用默认命名空间:::variable

例 3 命名空间的使用

#include <stdio.h>

namespace First
{
    int i = 0;
}

namespace Second
{
    int i = 1;

    namespace Internal
    {
        struct P
        {
            int x;
            int y;
        };
    }
}

int main()
{
    using namespace First;
    using Second::Internal::P;

    printf("First::i = %d\n", i);
    printf("Second::i = %d\n", Second::i);

    P p = {2, 3};

    printf("p.x = %d\n", p.x);
    printf("p.y = %d\n", p.y);

    return 0;
}

小结:
C++中内置了动态内存分配的专用关键字
C++中的动态内存分配可以同时进行初始化
C++中的动态内存分配是基于类型进行的
C++中命名空间概念用于解决名称冲突问题

第 11 课 新型的类型转换

强制类型转换
c 方式的强制类型转换
(type)(expression)
type(expression)
typedef void(PF)(int);
struct pointer
{
int x;
int y;
};
int v=0x12345;
pf f=(pf)v;
char c=char(v);
pointer p=(pointer )v;

例 1 粗暴的类型转换

#include <stdio.h>

typedef void(PF)(int);

struct Point
{
    int x;
    int y;
};

int main()
{
    int v = 0x12345;
    PF* pf = (PF*)v;
    char c = char(v);
    Point* p = (Point*)v;

    pf(5);

    printf("p->x = %d\n", p->x);
    printf("p->y = %d\n", p->y);

    return 0;
}

C 方式强制类型转换存在的问题
过于粗暴
任意类型之间都可以进行转换,编译器很难判断其正确性
难于定位
在源码中无法快速定位所有使用强制类型转换的语句

强制类型转换在实际工程中是很难完全避免的。
如何进行更加安全可靠的转换?

新式类型转换
C++将强制类型转换分为 4 种不同的类型
强制类型转换
static_cast
const_cast
dynamic_cast
reinterpret_cast
用法:xxx_cast(Expression)

static_cast 强制类型转换
用于基本类型间的转换
不能用于基本类型指针间的转换
用于有继承关系类对象之间的转换和类指针之间的转换

const_cast 强制类型转换
用于去除变量的只读属性
强制转换的目标类型必须是指针或引用

reinterpret_cast 强制类型转换
用于指针类型间的强制转换
用于整数和指针类型间的强制转换

dynamic_cast 强制类型转换
用于有继承关系的类指针间的转换
用于有交叉关系的类指针间的转换
具有类型检查的功能
需要虚函数的支持

例 2 新式类型转换初探

#include <stdio.h>

void static_cast_demo()
{
    int i = 0x12345;
    char c = 'c';
    int* pi = &i;
    char* pc = &c;

    c = static_cast<char>(i);
    //pc = static_cast<char*>(pi);  //error
    printf("%p\n",c);
}

void const_cast_demo()
{
    const int& j = 1;
    int& k = const_cast<int&>(j);

    const int x = 2;
    int& y = const_cast<int&>(x);

    //int z = const_cast<int>(x);  //error

    k = 5;

    printf("k = %d\n", k);
    printf("j = %d\n", j);

    y = 8;

    printf("x = %d\n", x);
    printf("y = %d\n", y);
    printf("&x = %p\n", &x);
    printf("&y = %p\n", &y);
}

void reinterpret_cast_demo()
{
    int i = 0;
    char c = 'c';
    int* pi = &i;
    char* pc = &c;

    pc = reinterpret_cast<char*>(pi);
    pi = reinterpret_cast<int*>(pc);
    pi = reinterpret_cast<int*>(i);
    //c = reinterpret_cast<char>(i); //error
}

void dynamic_cast_demo()
{
    int i = 0;
    int* pi = &i;
    //char* pc = dynamic_cast<char*>(pi);  //error
}

int main()
{
    static_cast_demo();
    const_cast_demo();
    reinterpret_cast_demo();
    dynamic_cast_demo();

    return 0;
}

小结
C 方式的强制类型转换
过于粗暴
潜在的问题不易被发现
不易在代码中定位
新式类型转换以 C++关键字的方式出现
编译器能够帮助检查潜在的问题
非常方便的在代码中定位
支持动态类型识别(dynamic_cast)

第 12 课 经典问题解析

const:
什么时候为只读常量?
什么时候是常量?

const 常量的判别准则
只有用字面量初始化 const 常量才会进入符号表
使用其它变量初始化的 const 常量仍然是只读变量
被 volatile 修饰的 const 常量不会进入符号表
在编译期间不能直接确定初始值的 const 标识符,都被作为只读变量处理

const 引用的类型与初始化变量的类型
相同:初始化变量成为只读变量
不同:生成一个新的只读变量

例 1 const 典型问题分析

#include <stdio.h>

int main()
{
    const int x = 1;
    const int& rx = x;

    int& nrx = const_cast<int&>(rx);

    nrx = 5;

    printf("x = %d\n", x);
    printf("rx = %d\n", rx);
    printf("nrx = %d\n", nrx);
    printf("&x = %p\n", &x);
    printf("&rx = %p\n", &rx);
    printf("&nrx = %p\n", &nrx);

    volatile const int y = 2;
    int* p = const_cast<int*>(&y);

    *p = 6;

    printf("y = %d\n", y);
    printf("p = %p\n", p);

    const int z = y;

    p = const_cast<int*>(&z);

    *p = 7;

    printf("z = %d\n", z);
    printf("p = %p\n", p);

    char c = 'c';
    char& rc = c;
    const int& trc = c;

    rc = 'a';

    printf("c = %c\n", c);
    printf("rc = %c\n", rc);
    printf("trc = %c\n", trc);

    return 0;
}

引用与指针有什么关系?
如何理解“引用的本质就是指针常量”?

指针是一个变量
值是一个内存地址,不需要初始化,可以保存不同的地址
通过指着可以访问对应内存地址中的值
指着可以被 const 修饰成为常量或者只读变量
引用只是一个变量的新名字
对引用的操作(赋值,取地址等)都会传递到代表的变量上
const 引用使其代表的变量具有只读属性
引用必须在定义时初始化,之后无法代表其他变量

从使用 c++语言的角度来看
引用与指针没有任何关系
引用是变量的新名字,操作引用就是操作对应的变量
从 c++编译器的角度来看
为了支持新概念“引用”必须要一个有效的解决方案
在编译器内部,使用指针常量来实现“引用”
因此,“引用”在定义时必须初始化

在工程项目开发中
当进行 C++编程时,直接站在使用角度看待问题,与指针毫无关系,引用就是变量的别

当对 C++代码进行调试分析时,一些特殊情况,可以考虑站在 C++编译器的角度看待引

下面的代码有问题吗?
int a=1;
int b=2;
int *pc=new int(3);
int &arr[]={a,b,*pc};

例 2 引用典型问题分析

#include <stdio.h>

int a = 1;

struct SV
{
    int& x;
    int& y;
    int& z;
};

int main()
{
    int b = 2;
    int* pc = new int(3);
    SV sv = {a, b, *pc};
    int& array[] = {a, b, *pc}; // &array[1] - &array[0] = ?  Expected ==> 4

    printf("&sv.x = %p\n", &sv.x);
    printf("&sv.y = %p\n", &sv.y);
    printf("&sv.z = %p\n", &sv.z);

    delete pc;

    return 0;
}

小结:
指针是一个变量
引用是一个变量的新名字
const 引用能够生成新的只读变量
在编译器内部使用指针常量实现“引用”
编译时不能直接确定初始值的 const 标识符都是只读变量

第 13 课 进阶面向对象(上)

面向对象的意义在于
将日常生活中的思维方式引入程序设计中
将需求中的概念直观的映射到解决方案中
以模块为中心构建可复用的软件系统
提高软件产品的可维护性和可扩展性

类和对象是面向对象中的两个基本概念
类:指的是一类事物,是一个抽象的概念
对象:指的是属于某个类的具体实体
类是一种模型,这种模型可以创建出不同的对象实体
对象实体是类模型的一个具体实例

一个类可以有很多对象,而一个对象必然属于某个类
类和对象的意义
类用于抽象的描述一类事物所持有的属性和行为
对象时具体的事物,拥有所属类中描述的一切属性和行为

小结:
面向对象是当今软件开发中的重要方法
类和对象是面向对象理论中的基本概念
类和对象均来源于日常生活中
每个类可以有多个对象
每个对象必然属于某个类

第 14 课 进阶面向对象(下)
类之间的基本关系
继承
从已存在类细分出来的类和原类之间具有继承关系
继承的类(子类)拥有原类(父类)的所有属性和行为
组合
一些类的存在必须依赖于其他的类,这种关系叫组合
组合的类在某一个局部上由其他的类组成

例 1 类的表示法

#include <stdio.h>

struct Biology {
    bool living;
};

struct Animal : Biology {
    bool movable;
    void findFood() { }
};

struct Plant : Biology {
    bool growable;
};

struct Beast : Animal {
    void sleep() { }
};

struct Human : Animal {
    void sleep() { }
    void work() { }
};


int main()
{
    return 0;
}

小结:
类之间可以存在继承关系或组合关系
继承关系中子类拥有父类的一切属性和行为
组合关系式类之间整体和部分的关系
类及类之间的关系可以有不同的表示法
编译器对类的表示法有具体的要求

第 15 课 类与封装的概念
类通常分为以下两个部分
类的实现细节
类的使用方式

当使用类时,不需要关心其实现细节
当创建类时,才需要考虑其内部实现细节

封装的基本概念
根据经验:并不是类的每个属性都是对外公开的
而一些类的属性是对外公开的
必须在类的表示法中定义属性和行为的公开级别
类似文件系统中文件的权限

C++中类的封装
成员变量:C++中用于表示类属性的变量
成员函数:C++中用于表示类行为的函数
C++中可以给成员变量和成员函数定义访问级别
public
成员变量和成员函数可以在类的内部和外界访问和调用
private
成员变量和成员函数只能在类的内部被访问和调用

例 1 类成员的访问属性

#include <stdio.h>

#include <stdio.h>

struct Biology 
{
    bool living;
};

struct Animal : Biology 
{
    bool movable;

    void findFood()
    { 
    }
};

struct Plant : Biology 
{
    bool growable;
};

struct Beast : Animal 
{
    void sleep() 
    { 
    }
};

struct Human : Animal 
{
    void sleep() 
    { 
        printf("I'm sleeping...\n");
    }

    void work() 
    { 
        printf("I'm working...\n");
    }
};

struct Girl : Human
{
private:
    int age;
    int weight;
public:
    void print()
    {
        age = 22;
        weight = 48;

        printf("I'm a girl, I'm %d years old.\n", age);
        printf("My weight is %d kg.\n", weight);
    }
};

struct Boy : Human
{
private:
    int height;
    int salary;
public:
    int age;
    int weight;

    void print()
    {
        height = 175;
        salary = 9000;

        printf("I'm a boy, my height is %d cm.\n", height);
        printf("My salary is %d RMB.\n", salary);
    }    
};

int main()
{
    Girl g;
    Boy b;

    g.print();

    b.age = 19;
    b.weight = 120;
    //b.height = 180;

    b.print();

    return 0;
}

类成员的作用域
类成员的作用域只在类的内部,外部无法直接访问
成员函数可以直接访问成员变量和调用成员函数
类的外部可以通过类变量访问 public 成员
类成员的作用域与访问级别没有关系
C++中用 struct 定义类中所有成员默认为 public

例 2 类成员的作用域

#include <stdio.h>

int i = 1;

struct Test
{
private:
    int i;

public:
    int j;

    int getI()
    {
        i = 3;

        return i;
    }
};

int main()
{
    int i = 2;

    Test test;

    test.j = 4;

    printf("i = %d\n", i);              // i = 2;
    printf("::i = %d\n", ::i);          // ::i = 1;
    // printf("test.i = %d\n", test.i);    // Error
    printf("test.j = %d\n", test.j);    // test.j = 4
    printf("test.getI() = %d\n", test.getI());  // test.getI() = 3

    return 0;
}

小结:
类通常可以分为使用方式和内部细节两部分
类的封装机制使得使用方式和内部细节相分离
C++中通过定义类成员的访问级别实现封装机制
public 成员可以在类的内部和外界访问和调用
private 成员只能在类的内部被访问和调用

第 16 课 类的真正形态

struct 在 c 语言中已经有了自己的含义,必须继续兼容
在 C++中提供了新的关键字 class 用于类的定义
class 和 struct 的用法是完全相同的

class 和 struct 有什么区别?
在用 struct 定义类时,所有成员的默认访问级别为 public
在用 class 定义类时,所有成员的默认访问级别为 private
struct A
{
int i;
int getI()
{
return i;
}
};
class B
{
int i;
int getI()
{
return i;
}
};

例 1 class 的初探

#include <stdio.h>

struct A
{
    // defualt to public
    int i;
    // defualt to public
    int getI()
    {
        return i;
    }
};

class B
{
    // defualt to private
    int i;
    // defualt to private
    int getI()
    {
        return i;
    }
};

int main()
{
    A a;
    B b;

    a.i = 4;

    printf("a.getI() = %d\n", a.getI());

    b.i = 4;

    printf("b.getI() = %d\n", b.getI());

    return 0;
}

小实例
需求:开发一个用于四则运算的类
提供 setOperator 函数设置运算类型
提供 setParameter 函数设置运算参数
提供 result 函数进行运算
其返回值表示运算的合法性
通过引用参数返回结果

C++中的类支持声明和实现的分离
将类的实现和定义分开
.h 头文件中只有类的声明
成员变量和成员函数的声明
.cpp 源文件中完成类的其它实现
成员函数的具体实现

例 2 Operator 类的分析

//"Operator.h"
#ifndef _OPERATOR_H_
#define _OPERATOR_H_

class Operator
{
private:
    char mOp;
    double mP1;
    double mP2;

public:
    bool setOperator(char op);
    void setParameter(double p1, double p2);
    bool result(double& r);
};

#endif
#include <stdio.h>
#include "Operator.h"

int main()
{
    Operator op;
    double r = 0;

    op.setOperator('/');
    op.setParameter(9, 3);

    if( op.result(r) )
    {
        printf("r = %lf\n", r);
    }
    else
    {
        printf("Calculate error!\n");
    }

    return 0;
}
#include "Operator.h"

bool Operator::setOperator(char op)
{
    bool ret = false;

    if( (op == '+') || (op == '-') || (op == '*') || (op == '/') )
    {
        ret = true;
        mOp = op;
    }
    else
    {
        mOp = '\0';
    }

    return ret;
}

void Operator::setParameter(double p1, double p2)
{
    mP1 = p1;
    mP2 = p2;
}

bool Operator::result(double& r)
{
    bool ret = true;

    switch( mOp )
    {
        case '/':
            if( (-0.000000001 < mP2) && (mP2 < 0.000000001) )
            {
                ret = false;
            }
            else
            {
                r = mP1 / mP2;
            }
            break;
        case '+':
            r = mP1 + mP2;
            break;
        case '*':
            r = mP1 * mP2;
            break;
        case '-':
            r = mP1 - mP2;
            break;
        default:
            ret = false;
            break;
    }

    return ret;
}

C++引进了新的关键字 class 用于定义类
struct 和 class 的区别在于默认访问级别的不同
C++中类支持声明和实现的分离
在头文件中声明类
在源文件中实现类

第 17 课 对象的构造(上)

对象中成员变量的初始值是多少?

下面的类定义中成员变量 i 和 j 的初始值为什么?
class Test
{
private:
int i;
int j;
public:
int getI(){return i;}
int getJ(){return j;}
};

例 1 成员变量的初始值

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
};

Test gt;

int main()
{
    printf("gt.i = %d\n", gt.getI());
    printf("gt.j = %d\n", gt.getJ());

    Test t1;

    printf("t1.i = %d\n", t1.getI());
    printf("t1.j = %d\n", t1.getJ());

    Test* pt = new Test;

    printf("pt->i = %d\n", pt->getI());
    printf("pt->j = %d\n", pt->getJ());

    delete pt;

    return 0;
}

从程序设计的角度,对象只是变量,因此
在栈上创建对象时,成员变量初始为随机值
在堆上创建对象时,成员变量初始为随机值
在静态存储区创建对象时,成员变量初始为 0 值

一般而言,对象都需要一个确定的初始状态
解决方案
在类中提供一个 public 和 initialize 函数
对象创建后立即调用 initialize 函数进行初始化
class Test
{
private:
int i;
int j;
public:
void initialize() {i=0;j=0;}
int getI() {return i;}
int getJ() {return j;}
};

例 2 初始化函数

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
    void initialize()
    {
        i = 1;
        j = 2;
    }
};

Test gt;

int main()
{
    gt.initialize();

    printf("gt.i = %d\n", gt.getI());
    printf("gt.j = %d\n", gt.getJ());

    Test t1;

    //t1.initialize();

    printf("t1.i = %d\n", t1.getI());
    printf("t1.j = %d\n", t1.getJ());

    t1.initialize();

    Test* pt = new Test;

    pt->initialize();

    printf("pt->i = %d\n", pt->getI());
    printf("pt->j = %d\n", pt->getJ());

    delete pt;

    return 0;
}

存在的问题
initialize 只是一个普通函数,必须显示调用
如果未调用 initialize 函数,运行结果是不确定的

C++中可以定义与类名相同的特殊成员函数
这种成员函数叫做构造函数
构造函数没有任何返回类型的声明
构造函数在对象定义时自动时自动被调用

例 3 构造函数初探

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
    Test()
    {
        printf("Test() Begin\n");

        i = 1;
        j = 2;

        printf("Test() End\n");
    }
};

Test gt;

int main()
{
    printf("gt.i = %d\n", gt.getI());
    printf("gt.j = %d\n", gt.getJ());

    Test t1;

    printf("t1.i = %d\n", t1.getI());
    printf("t1.j = %d\n", t1.getJ());

    Test* pt = new Test;

    printf("pt->i = %d\n", pt->getI());
    printf("pt->j = %d\n", pt->getJ());

    delete pt;

    return 0;
}

小结:
每个对象在使用之前都应该初始化
类的构造函数用于对象的初始化
构造函数与类同名并且没有返回值
构造函数在对象定义时自动被调用

第 18 课 对象的构造(中)

带有参数的构造函数
构造函数可以根据需要定义参数
一个类中可以存在多个重载的构造函数
构造函数的重载遵循 C++重载的规则
class Test
{
public:
Test(int v)
{
//use v to initialize member
}
};

对象定义和声明不同
对象定义—申请对象的空间并调用构造函数
对象声明—告诉编译器存在这样一个对象
Test t; //定义对象并调用构造函数
int main()
{
//告诉编译器存在名为 t 的 Test 对象
extern Test t;
return 0;
}

构造函数的自动调用
class Test
{
public:
Test(){}
Test(int v){}
};
int main()
{
Test t; //调用 Test()
Test t1(1); //调用 Test(int v)
Test t2=1; //调用 Test(int v)
return 0;
}

例 1 带参数的构造函数

#include <stdio.h>

class Test
{
public:
    Test() 
    { 
        printf("Test()\n");
    }
    Test(int v) 
    { 
        printf("Test(int v), v = %d\n", v);
    }
};

int main()
{
    Test t;      // 调用 Test()
    Test t1(1);  // 调用 Test(int v)
    Test t2 = 2; // 调用 Test(int v)

    int i(100);

    printf("i = %d\n", i);

    return 0;
}

构造函数的调用
一般情况下,构造函数在对象定义时自动调用
一些特殊情况下,需要手工调用构造函数
如何创建一个对象数组?

例 2 构造函数的手动调用

#include <stdio.h>

class Test
{
private:
    int m_value;
public:
    Test() 
    { 
        printf("Test()\n");

        m_value = 0;
    }
    Test(int v) 
    { 
        printf("Test(int v), v = %d\n", v);

        m_value = v;
    }
    int getValue()
    {
        return m_value;
    }
};

int main()
{
    Test ta[3] = {Test(), Test(1), Test(2)};      

    for(int i=0; i<3; i++)
    {
        printf("ta[%d].getValue() = %d\n", i , ta[i].getValue());
    }

    Test t = Test(100);

    printf("t.getValue() = %d\n", t.getValue());

    return 0;
}

需求:开发一个数组类解决原生数组的安全性问题
提供函数获取数组长度
提供函数获取数组元素
提供函数设置数组元素

例 3 数组类的实现

#ifndef _INTARRAY_H_
#define _INTARRAY_H_

class IntArray
{
private:
    int m_length;
    int* m_pointer;
public:
    IntArray(int len);
    int length();
    bool get(int index, int& value);
    bool set(int index ,int value);
    void free();
};

#endif
#include "IntArray.h"

IntArray::IntArray(int len)
{
    m_pointer = new int[len];

    for(int i=0; i<len; i++)
    {
        m_pointer[i] = 0;
    }

    m_length = len;
}

int IntArray::length()
{
    return m_length;
}

bool IntArray::get(int index, int& value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        value = m_pointer[index];
    }

    return ret;
}

bool IntArray::set(int index, int value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        m_pointer[index] = value;
    }

    return ret;
}

void IntArray::free()
{
    delete[]m_pointer;
}
#include <stdio.h>
#include "IntArray.h"

int main()
{
    IntArray a(5);    

    for(int i=0; i<a.length(); i++)
    {
        a.set(i, i + 1);
    }

    for(int i=0; i<a.length(); i++)
    {
        int value = 0;

        if( a.get(i, value) )
        {
            printf("a[%d] = %d\n", i, value);
        }
    }

    a.free();

    return 0;
}

小结:
构造函数可以根据需要定义参数
构造函数之间可以存在重载关系
构造函数遵循 C++中重载函数的规则
对象定义时会触发构造函数的调用
在一些情况下需要手动调用构造函数

第 19 课 对象的构造(下)

两个特殊的构造函数
无参构造函数
没有参数的构造函数
拷贝构造函数
参数为 const class_name&的构造函数

两个特殊的构造函数
无参构造函数
当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为

拷贝构造函数
当类中没有定义拷贝构造函数时,编译器默认提供了一个拷贝构造函数,简单的进
行成员变量的值复制

例 1 特殊的构造函数

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI()
    {
        return i;
    }
    int getJ()
    {
        return j;
    }
    /*Test(const Test& t)
    {
        i = t.i;
        j = t.j;
    }
    Test()
    {
    }*/
};

int main()
{
    Test t1;
    Test t2 = t1;

    printf("t1.i = %d, t1.j = %d\n", t1.getI(), t1.getJ());
    printf("t2.i = %d, t2.j = %d\n", t2.getI(), t2.getJ());

    return 0;
}

拷贝构造函数的意义
兼容 C 语言的初始化方式
初始化行为能够符合预期的逻辑
拷贝构造函数的意义
浅拷贝
拷贝后对象物理状态相同
深拷贝
拷贝后对象的逻辑状态相同
编译器提供的拷贝构造函数只进行浅拷贝

例 2 对象的初始化

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
    int* p;
public:
    int getI()
    {
        return i;
    }
    int getJ()
    {
        return j;
    }
    int* getP()
    {
        return p;
    }
    Test(const Test& t)
    {
        i = t.i;
        j = t.j;
        p = new int;

        *p = *t.p;
    }
    Test(int v)
    {
        i = 1;
        j = 2;
        p = new int;

        *p = v;
    }
    void free()
    {
        delete p;
    }
};

int main()
{
    Test t1(3);
    Test t2(t1);

    printf("t1.i = %d, t1.j = %d, *t1.p = %d\n", t1.getI(), t1.getJ(), *t1.getP());
    printf("t2.i = %d, t2.j = %d, *t2.p = %d\n", t2.getI(), t2.getJ(), *t2.getP());

    t1.free();
    t2.free();

    return 0;
}

什么时候需要进行深拷贝?
对象有成员指代了系统中的资源
成员指向了动态内存空间
成员打开了外存中的文件
成员使用了系统中的网络端口
。。。。

一般性原则
自定义拷贝构造函数,必然需要实现深拷贝

例 3 数组类的改进

#ifndef _INTARRAY_H_
#define _INTARRAY_H_

class IntArray
{
private:
    int m_length;
    int* m_pointer;
public:
    IntArray(int len);
    IntArray(const IntArray& obj);
    int length();
    bool get(int index, int& value);
    bool set(int index ,int value);
    void free();
};

#endif
#include "IntArray.h"

IntArray::IntArray(int len)
{
    m_pointer = new int[len];

    for(int i=0; i<len; i++)
    {
        m_pointer[i] = 0;
    }

    m_length = len;
}

IntArray::IntArray(const IntArray& obj)
{
    m_length = obj.m_length;

    m_pointer = new int[obj.m_length];

    for(int i=0; i<obj.m_length; i++)
    {
        m_pointer[i] = obj.m_pointer[i];
    }
}

int IntArray::length()
{
    return m_length;
}

bool IntArray::get(int index, int& value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        value = m_pointer[index];
    }

    return ret;
}

bool IntArray::set(int index, int value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        m_pointer[index] = value;
    }

    return ret;
}

void IntArray::free()
{
    delete[]m_pointer;
}
#include <stdio.h>
#include "IntArray.h"

int main()
{
    IntArray a(5);    

    for(int i=0; i<a.length(); i++)
    {
        a.set(i, i + 1);
    }

    for(int i=0; i<a.length(); i++)
    {
        int value = 0;

        if( a.get(i, value) )
        {
            printf("a[%d] = %d\n", i, value);
        }
    }

    IntArray b = a;

    for(int i=0; i<b.length(); i++)
    {
        int value = 0;

        if( b.get(i, value) )
        {
            printf("b[%d] = %d\n", i, value);
        }
    }

    a.free();
    b.free();

    return 0;
}

小结:
C++编译器会默认提供构造函数
无参构造函数用于定义对象的默认初始化状态
拷贝构造函数在创建对象时拷贝对象的状态
对象的拷贝有浅拷贝和深拷贝两种方式
浅拷贝使得对象的物理状态相同
深拷贝使得对象的逻辑状态相同

第 20 课 初始化列表的使用

类中是否可以定义 const 成员?

下面的类定义是否合法?
如果合法,ci 的值是什么,存储在哪里?
class Test
{
private:
const int ci;
public:
int getCI() {return ci;}
};

例 1 类中的 const 成员

#include <stdio.h>

class Test
{
private:
    const int ci;
public:
    Test()
    {
        ci = 10;
    }
    int getCI() 
    { 
        return ci; 
    }
};


int main()
{
    Test t;

    printf("t.ci = %d\n", t.getCI());

    return 0;
}

C++中提供了初始化列表对成员变量进行初始化
语法规则
classname::classname():m1(v1),m2(v1,v2),m3(v3)
{
}

注意事项
成员的初始化顺序与成员的声明顺序相同
成员的初始化顺序与初始化列表中的位置无关
初始化列表先于构造函数的函数体执行

例 2 初始化列表的使用

#include <stdio.h>

class Value
{
private:
    int mi;
public:
    Value(int i)
    {
        printf("i = %d\n", i);
        mi = i;
    }
    int getI()
    {
        return mi;
    }
};

class Test
{
private:
    Value m2;
    Value m3;
    Value m1;
public:
    Test() : m1(1), m2(2), m3(3)
    {
        printf("Test::Test()\n");
    }
};


int main()
{
    Test t;

    return 0;
}

类中的 const 成员会被分配空间的
类中的 const 成员的本质是只读属性
类中的 const 成员只能在初始化列表中指定初始值
编译器无法直接得到 const 成员的初始值,因此无法进入符号表成为真正意义上的常量

例 3 只读成员变量

#include <stdio.h>

class Value
{
private:
    int mi;
public:
    Value(int i)
    {
        printf("i = %d\n", i);
        mi = i;
    }
    int getI()
    {
        return mi;
    }
};

class Test
{
private:
    const int ci;
    Value m2;
    Value m3;
    Value m1;
public:
    Test() : m1(1), m2(2), m3(3), ci(100)
    {
        printf("Test::Test()\n");
    }
    int getCI()
    {
        return ci;
    }
    int setCI(int v)
    {
        int* p = const_cast<int*>(&ci);

        *p = v;
    }
};


int main()
{
    Test t;

    printf("t.ci = %d\n", t.getCI());

    t.setCI(10);

    printf("t.ci = %d\n", t.getCI());

    return 0;
}

初始化与赋值不同
初始化:对正在创建的对象进行初值设置
赋值:对已经存在的对象进行值设置

小结:
类中可以使用初始化列表对成员进行初始化
初始化列表先于构造函数体执行
类中可以定义 const 成员变量
const 成员变量必须在初始化列表中指定初值
const 成员变量为只读变量

第 21 课 对象的构造顺序

C++中的类可以定义多个对象,那么对象的构造顺序是怎么样的?

对于局部对象
当程序执行流到达对象定义语句时进行构造

下面程序中的对象构造顺序是什么?
int i=0;
Test al=i;
while(i<3)
{
Test a2=++i;
}
if(i<4)
{
Test a=al;
}
else
{
Test a(100);
}

例 1 局部对象的构造顺序

#include <stdio.h>

class Test
{
private:
    int mi;
public:
    Test(int i)
    {
        mi = i;
        printf("Test(int i): %d\n", mi);
    }
    Test(const Test& obj)
    {
        mi = obj.mi;
        printf("Test(const Test& obj): %d\n", mi);
    }
};

int main()
{
    int i = 0;
    Test a1 = i;    //Test(int i):0

    while( i < 3 )
    {
        Test a2 = ++i;  ////Test(int i):1,2,3
    }

    if( i < 4 )
    {
        Test a = a1;  //Test(const Test& obj):0
    }
    else
    {
        Test a(100);
    }

    return 0;
}

例 2 error

#include <stdio.h>

class Test
{
private:
    int mi;
public:
    Test(int i)
    {
        mi = i;
        printf("Test(int i): %d\n", mi);
    }
    Test(const Test& obj)
    {
        mi = obj.mi;
        printf("Test(const Test& obj): %d\n", mi);
    }
    int getMi()
    {
        return mi;
    }
};

int main()
{
    int i = 0;
    Test a1 = i; // Test(int i): 0

    while( i < 3 )
    {
        Test a2 = ++i; // Test(int i): 1, 2, 3
    }
goto End;       
        Test a(100);
End:
    printf("a.mi = %d\n", a.getMi());
    return 0;
}

对于堆对象
当程序执行流到达 new 语句时创建对象
使用 new 创建对象将自动触发构造函数的调用

下面程序中的对象构造顺序是什么?
int i=0;
Test *al=new Test(i);
while(++i<10)
if(i%2)
new Test(i);
if(i<4)
new Test(*al);
else
new Test(100);

例 3 堆对象的构造顺序

#include <stdio.h>

class Test
{
private:
    int mi;
public:
    Test(int i)
    {
        mi = i;
        printf("Test(int i): %d\n", mi);
    }
    Test(const Test& obj)
    {
        mi = obj.mi;
        printf("Test(const Test& obj): %d\n", mi);
    }
    int getMi()
    {
        return mi;
    }
};

int main()
{
    int i = 0;
    Test* a1 = new Test(i); // Test(int i): 0

    while( ++i < 10 )
        if( i % 2 )
            new Test(i); // Test(int i): 1, 3, 5, 7, 9

    if( i < 4 )
        new Test(*a1);
    else
        new Test(100); // Test(int i): 100

    return 0;
}

对于全局对象
对象的构造顺序是不确定的
不同的编译器使用不同的规则确定构造顺序

例 4 全局对象的构造顺序

#include "test.h"

Test t4("t4");

int main()
{
    Test t5("t5");
}
#include "test.h"

Test t1("t1");
#include "test.h"

Test t2("t2");
#include "test.h"

Test t3("t3");
#ifndef _TEST_H_
#define _TEST_H_

#include <stdio.h>

class Test
{
public:
    Test(const char* s)
    {
        printf("%s\n", s);
    }
};

#endif

小结:
局部对象的构造顺序依赖于程序的执行流
堆对象的构造顺序依赖于 new 的使用顺序
全局对象的构造顺序是不确定的

第 22 课 对象的销毁

生活中的对象都是被初始化后才上市的
生活中的对象呗销毁前会做一些清理工作
问题:
C++中如何清理需要销毁的对象

一般而言,需要销毁的对象都应该做清理
解决方案
为每个类提供一个 public 的 free 函数
对象不再需要时立即调用 free 函数进行清理
class test
{
int *p;
public:
test()
{
p=new int;
}
void free()
{
delete p;
}
};

例 1 IntArray 使用分析

#ifndef _INTARRAY_H_
#define _INTARRAY_H_

class IntArray
{
private:
    int m_length;
    int* m_pointer;
public:
    IntArray(int len);
    IntArray(const IntArray& obj);
    int length();
    bool get(int index, int& value);
    bool set(int index ,int value);
    void free();
};

#endif
#include "IntArray.h"

IntArray::IntArray(int len)
{
    m_pointer = new int[len];

    for(int i=0; i<len; i++)
    {
        m_pointer[i] = 0;
    }

    m_length = len;
}

IntArray::IntArray(const IntArray& obj)
{
    m_length = obj.m_length;

    m_pointer = new int[obj.m_length];

    for(int i=0; i<obj.m_length; i++)
    {
        m_pointer[i] = obj.m_pointer[i];
    }
}

int IntArray::length()
{
    return m_length;
}

bool IntArray::get(int index, int& value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        value = m_pointer[index];
    }

    return ret;
}

bool IntArray::set(int index, int value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        m_pointer[index] = value;
    }

    return ret;
}

void IntArray::free()
{
    delete[]m_pointer;
}
#include <stdio.h>
#include "IntArray.h"

int main()
{
    IntArray a(5);    

    for(int i=0; i<a.length(); i++)
    {
        a.set(i, i + 1);
    }

    for(int i=0; i<a.length(); i++)
    {
        int value = 0;

        if( a.get(i, value) )
        {
            printf("a[%d] = %d\n", i, value);
        }
    }

    IntArray b = a;

    for(int i=0; i<b.length(); i++)
    {
        int value = 0;

        if( b.get(i, value) )
        {
            printf("b[%d] = %d\n", i, value);
        }
    }

    a.free();
    b.free();

    return 0;
}

存在的问题
free 只是一个普通函数,必须显示的调用
对象销毁前没有做清理,很可能造成资源泄漏
c++编译器是否能够自动调用某个特殊的函数进行对象的清理?

析构函数
C++的类中可以定义一个特殊的清理函数
这个特殊的清理函数叫做析构函数
析构函数的功能与析构函数相反
定义:~classname
析构函数没有参数也没有返回值类型声明
析构函数在对象销毁时自动被调用

例 2 析构函数使用初探

#include <stdio.h>

class Test
{
    int mi;
public:
    Test(int i)
    {
        mi = i;
        printf("Test(): %d\n", mi);
    }
    ~Test()
    {
        printf("~Test(): %d\n", mi);
    }
};

int main()
{
    Test t(1);

    Test* pt = new Test(2);

    delete pt;

    return 0;
}

例 3 IntArray 类的进化

#ifndef _INTARRAY_H_
#define _INTARRAY_H_

class IntArray
{
private:
    int m_length;
    int* m_pointer;
public:
    IntArray(int len);
    IntArray(const IntArray& obj);
    int length();
    bool get(int index, int& value);
    bool set(int index ,int value);
    ~IntArray();
};

#endif
#include "IntArray.h"

IntArray::IntArray(int len)
{
    m_pointer = new int[len];

    for(int i=0; i<len; i++)
    {
        m_pointer[i] = 0;
    }

    m_length = len;
}

IntArray::IntArray(const IntArray& obj)
{
    m_length = obj.m_length;

    m_pointer = new int[obj.m_length];

    for(int i=0; i<obj.m_length; i++)
    {
        m_pointer[i] = obj.m_pointer[i];
    }
}

int IntArray::length()
{
    return m_length;
}

bool IntArray::get(int index, int& value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        value = m_pointer[index];
    }

    return ret;
}

bool IntArray::set(int index, int value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        m_pointer[index] = value;
    }

    return ret;
}

IntArray::~IntArray()
{
    delete[]m_pointer;
}
#include <stdio.h>
#include "IntArray.h"

int main()
{
    IntArray a(5);    

    for(int i=0; i<a.length(); i++)
    {
        a.set(i, i + 1);
    }

    for(int i=0; i<a.length(); i++)
    {
        int value = 0;

        if( a.get(i, value) )
        {
            printf("a[%d] = %d\n", i, value);
        }
    }

    IntArray b = a;

    for(int i=0; i<b.length(); i++)
    {
        int value = 0;

        if( b.get(i, value) )
        {
            printf("b[%d] = %d\n", i, value);
        }
    }

    return 0;
}

析构函数的定义准则
当类中自定义了构造函数,并且构造函数中使用了系统资源(如:内存申请,文件打开,
等),则需要自定义析构函数

小结:
析构函数时对象销毁时进行清理的特殊函数
析构函数在对象销毁时自动被调用
析构函数就是对象释放系统资源的保障

第 23 课 神秘的临时变量

下面的程序输出什么?为什么?
class Test
{
int mi;
public:
Test(int i)
{
mi=i;
}
Test()
{
Test(0);
}
void print()
{
printf(“mi=%d\n”,mi);
}
};
int main()
{
Test t;
t.printf();
return 0;
}

例 1 有趣的问题

#include <stdio.h>

class Test {
    int mi;
public:
    Test(int i) {
        mi = i;
    }
    Test() {
        Test(0);
    }
    void print() {
        printf("mi = %d\n", mi);
    }
};


int main()
{
    Test t;

    t.print();

    return 0;
}

例 2 临时对象

#include <stdio.h>

class Test {
    int mi;
    void init(int i)
    {
        mi=i;
    }
public:
    Test(int i) {
    printf("Test(int i)\n");
        init (i);
    }
    Test() {
        printf("Test()\n");
        init(0);
    }
    void print() {
        printf("mi = %d\n", mi);
    }
    ~Test()
    {
    printf("~Test()\n");
    }
};


int main()
{
    printf("main begin\n");
    Test().print();
    Test(10).print();
    printf("main end\n");
    return 0;
}

程序意图
在 Test()中以 0 作为参数调用 Test(int i)
将成员变量 mi 的初始值设置为 0
运行结果
成员变量 mi 的值为随机值

构造函数时一个特殊的函数
是否可以直接调用?
是否可以在构造函数中调用构造函数?
直接调用构造函数的行为是什么?

直接调用构造函数将产生一个临时对象
临时对象的生命周期只有一条语句的时间
临时对象的作用域只在一条语句中
临时对象时 C++中值得警惕的灰色地带

例 3 解决方案

#include <stdio.h>

class Test {
    int mi;

    void init(int i)
    {
        mi = i;
    }
public:
    Test(int i) {
        init(i);
    }
    Test() {
        init(0);
    }
    void print() {
        printf("mi = %d\n", mi);
    }
};


int main()
{
    Test t;

    t.print();

    return 0;
}

现代 C++编译器在不影响最终执行结果的前提下,会尽力减少临时对象的产生。

例 4 神秘的临时对象

#include <stdio.h>

class Test
{
    int mi;
public:
    Test(int i)
    {
        printf("Test(int i) : %d\n", i);
        mi = i;
    }
    Test(const Test& t)
    {
        printf("Test(const Test& t) : %d\n", t.mi);
        mi = t.mi;
    }
    Test()
    {
        printf("Test()\n");
        mi = 0;
    }
    int print()
    {
        printf("mi = %d\n", mi);
    }
    ~Test()
    {
        printf("~Test()\n");
    }
};

Test func()
{
    return Test(20);
}

int main()
{
    Test t = Test(10); // ==> Test t = 10;
    Test tt = func();  // ==> Test tt = Test(20); ==> Test tt = 20;

    t.print();
    tt.print();

    return 0;
}

小结:
直接调用构造函数将产生一个临时对象
临时对象是性能瓶颈,也是 bug 的来源之一
现在 c++编译器会尽力避开临时对象
实际工程开发中需要人为的避开临时对象

第 24 课 经典问题解析二

当程序中存在多个对象的时候,如何确定这些对象的析构顺序?

单个对象创建时析构函数的调用顺序
1、调用父类的析构过程
2、调用成员变量的构造函数(调用顺序和声明顺序相同)
3、调用类自身的构造函数
析构函数与对应构造函数的调用顺序相反

多个对象析构时
析构顺序与构造顺序相反

例 1 构造与析构顺序

#include <stdio.h>

class Member
{
    const char* ms;
public:
    Member(const char* s)
    {
        printf("Member(const char* s): %s\n", s);

        ms = s;
    }
    ~Member()
    {
        printf("~Member(): %s\n", ms);
    }
};

class Test
{
    Member mA;
    Member mB;
public:
    Test() : mB("mB"), mA("mA")
    {
        printf("Test()\n");
    }
    ~Test()
    {
        printf("~Test()\n");
    }
};

Member gA("gA");

int main()
{
    /*
    Member(const char* s): gA
    Member(const char* s): mA
    Member(const char* s): mB
    Test()
    ~Test()
    ~Member(): mB
    ~Member(): mA
    ~Member(): gA
    */


    Test t;

    return 0;
}

对于栈对象和全局对象,类似于入栈与出栈的顺序,最后构造的对象被最先析构
堆对象的析构发生在使用 delete 的时候,与 delete 的使用顺序相关

cont
const 关键字能否修饰类的对象?

如果可以,有什么特性?

关于 const 对象的疑问
const 关键字能够修饰对象
const 修饰的对象为只读对象
只读对象的成员变量不允许被改变
只读对象是编译阶段的概念,运行时无效

C++中的 const 成员函数
const 对象只能调用 const 的成员函数
const 成员函数中只能调用 const 成员函数
const 成员函数中不能直接改成成员变量的值

const 成员函数的定义
type classname::function(type p)const
类中的函数声明与实际函数定义中都必须带 const 关键字

例 2 类的 const 函数初探

#include <stdio.h>

class Test
{
    int mi;
public:
    Test(int i);
    Test(const Test& t);
    int getMi()const;
};

Test::Test(int i)
{
    mi = i;
}

Test::Test(const Test& t)
{

}

int Test::getMi()const
{
    return mi;
}

int main()
{
    const Test t(1);
    printf("t.getMI()=%d\n",t.getMi());

    return 0;
}

成员函数和成员变量都是隶属于具体对象的吗?

从面向对象的角度
对象由属性(成员变量)和方法(成员函数)构成
从程序运行的角度
对象由数据和函数构成
数据可以位于栈,堆和全局数据区
函数只能位于代码段

结论
每一个对象拥有自己独立的属性(成员属性)
所有的对象共享类的方法(成员函数)
方法能够直接访问对象的属性
方法中隐藏参数 this 用于指代当前对象

例 3 成员函数的秘密

#include <stdio.h>

class Test
{
    int mi;
public:
    int mj;
    Test(int i);
    Test(const Test& t);
    int getMi();
    void print();
};

Test::Test(int i)
{
    mi = i;
}

Test::Test(const Test& t)
{
    mi = t.mi;
}

int Test::getMi()
{
    return mi;
}

void Test::print()
{
    printf("this = %p\n", this);
}

int main()
{
    Test t1(1);
    Test t2(2);
    Test t3(3);

    printf("t1.getMi() = %d\n", t1.getMi());
    printf("&t1 = %p\n", &t1);
    t1.print();

    printf("t2.getMi() = %d\n", t2.getMi());
    printf("&t2 = %p\n", &t2);
    t2.print();

    printf("t3.getMi() = %d\n", t3.getMi());
    printf("&t3 = %p\n", &t3);
    t3.print();

    return 0;
}

小结:
对象的析构顺序与构造顺序相反
const 关键字能够修饰度夏凝,得到只读对象
只读对象只能调用 const 成员函数
所有对象共享类的成员函数
隐藏 this 指针用于表示当前对象

第 25 课 类的静态成员变量

通过对象名能够访问 public 成员变量
每个对象的成员变量都是专属的
成员变量不能再对象之间共享

统计在程序运行期间某个类的对象数目
保证程序的安全性(不能使用全局变量)
随时可以获取当前对象的数目

例 1 解决方案的尝试

#include <stdio.h>

class Test
{
private:
    int mCount;
public:
    Test() : mCount(0)
    {
        mCount++;
    }
    ~Test()
    {
        --mCount;
    }
    int getCount()
    {
        return mCount;
    }
};

Test gTest;

int main()
{
    Test t1;
    Test t2;

    printf("count = %d\n", gTest.getCount());
    printf("count = %d\n", t1.getCount());
    printf("count = %d\n", t2.getCount());

    return 0;
}

例 2 解决方案的尝试

#include <stdio.h>
int gCount=0;
class Test
{
private:
    int mCount;
public:
    Test() : mCount(0)
    {
        gCount++;
    }
    ~Test()
    {
        --gCount;
    }
    int getCount()
    {
        return gCount;
    }
};

Test gTest;

int main()
{
    Test t1;
    Test t2;

    printf("count = %d\n", gTest.getCount());
    printf("count = %d\n", t1.getCount());
    printf("count = %d\n", t2.getCount());

    return 0;
}

在 C++中可以定义静态成员变量
静态成员变量属于整个类所有
静态成员变量的生命期不依赖于任何对象
可以通过类名直接访问公有静态成员变量
所有对象共享类的静态成员变量
可以通过对象名访问公有静态成员变量

静态成员变量的特性
在定义时直接通过 static 关键字修饰
静态成员变量需要在类外单独分配空间
静态成员变量在程序内部位于全局数据区
语法规则:
type classname::varname=value;

例 3 静态成员变量的使用

#include <stdio.h>

class Test
{
private:
    static int cCount;
public:
    Test()
    {
        cCount++;
    }
    ~Test()
    {
        --cCount;
    }
    int getCount()
    {
        return cCount;
    }
};

int Test::cCount = 0;

Test gTest;

int main()
{
    Test t1;
    Test t2;

    printf("count = %d\n", gTest.getCount());
    printf("count = %d\n", t1.getCount());
    printf("count = %d\n", t2.getCount());

    Test* pt = new Test();

    printf("count = %d\n", pt->getCount());

    delete pt;

    printf("count = %d\n", gTest.getCount());

    return 0;
}

小结:
类中可以通过 static 关键定义静态成员变量
静态成员变量隶属于类所有
每一个对象都可以访问静态成员变量
静态成员变量在全局数据区分配空间
静态成员变量的生命期为程序运行期

第 26 课 类的静态成员函数

统计在程序运行期间某个类的对象数目
保证程序的安全性(不能使用全局变量)
随时可以获取当前对象的数目(Failure)

例 1 解决方案的尝试

#include <stdio.h>

class Test
{
public:
    static int cCount;
public:
    Test()
    {
        cCount++;
    }
    ~Test()
    {
        --cCount;
    }
    int getCount()
    {
        return cCount;
    }
};

int Test::cCount = 0;


int main()
{
    printf("count = %d\n", Test::cCount);

    Test::cCount = 1000;

    printf("count = %d\n", Test::cCount);

    return 0;
}

我们需要什么?
不依赖对象就可以访问静态成员变量
必须保证静态成员变量的安全性
方便快捷的获取静态成员变量的值

在 C++中可以定义静态成员函数
静态成员函数时类中特殊的成员函数
静态成员函数属于整个类所有
可以通过类名直接访问公有静态成员函数
可以通过对象名访问公有静态成员函数

静态成员函数的定义
直接通过 static 关键字修饰成员函数
class test
{
public:
static void func(){}
static int func2();
};
int test::func2()
{
return 0;
}

例 2 静态成员函数示例

#include <stdio.h>

class Demo
{
private:
    int i;
public:
    int getI();
    static void StaticFunc(const char* s);
    static void StaticSetI(Demo& d, int v);
};

int Demo::getI()
{
    return i;
}

void Demo::StaticFunc(const char* s)
{
    printf("StaticFunc: %s\n", s);
}

void Demo::StaticSetI(Demo& d, int v)
{
    d.i = v;
}

int main()
{
    Demo::StaticFunc("main Begin...");

    Demo d;

    Demo::StaticSetI(d, 10);

    printf("d.i = %d\n", d.getI());

    Demo::StaticFunc("main End...");

    return 0;
}

静态成员函数 VS 普通成员函数

例 3 最后的解决方案

#include <stdio.h>

class Test
{
private:
    static int cCount;
public:
    Test()
    {
        cCount++;
    }
    ~Test()
    {
        --cCount;
    }
    static int GetCount()
    {
        return cCount;
    }
};

int Test::cCount = 0;

int main()
{
    printf("count = %d\n", Test::GetCount());

    Test t1;
    Test t2;

    printf("count = %d\n", t1.GetCount());
    printf("count = %d\n", t2.GetCount());

    Test* pt = new Test();

    printf("count = %d\n", pt->GetCount());

    delete pt;

    printf("count = %d\n", Test::GetCount());

    return 0;
}

小结:
静态成员函数是类中特殊的成员函数
静态成员函数没有隐藏的 this 参数
静态成员函数可以通过类名直接访问
静态成员函数只能直接访问静态成员变量(函数)

第 27 课 二阶构造模式

关于构造函数
类的构造函数用于对象的初始化
构造函数与类同名并且没有返回值
构造函数在对象定义时自动调用

如何判断构造函数的执行结果?
在构造函数中执行 return 语句会发生什么?
构造函数执行结束是否意味着对象构造成功?

例 1 异常的构造函数

#include <stdio.h>

class Test
{
    int mi;
    int mj;
    bool mStatus;
public:
    Test(int i, int j) : mStatus(false)
    {
        mi = i;

        return;

        mj = j;

        mStatus = true;
    }
    int getI()
    {
        return mi;
    }
    int getJ()
    {
        return mj;
    }
    int status()
    {
        return mStatus;
    }
};

int main()
{  
    Test t1(1, 2);

    if( t1.status() )
    {
        printf("t1.mi = %d\n", t1.getI());
        printf("t1.mj = %d\n", t1.getJ());

    }

    return 0;
}

构造函数
只提供自动初始化成员变量的机会
不能保证初始化逻辑一定成功
执行 return 语句后构造函数立即结束

构造函数能决定的只是对象的初始状态,而不是对象的诞生

半成品对象的概念
初始化操作不能按照预期完成而得到的对象
半成品对象是合法的 C++对象,也是 bug 的重要来源

例 2 半成品对象的危害

#ifndef _INTARRAY_H_
#define _INTARRAY_H_

class IntArray
{
private:
    int m_length;
    int* m_pointer;
public:
    IntArray(int len);
    IntArray(const IntArray& obj);
    int length();
    bool get(int index, int& value);
    bool set(int index ,int value);
    ~IntArray();
};

#endif
#include <stdio.h>
#include "IntArray.h"

int main()
{
    IntArray a(5);    
    printf("a.length=%d\n",a.length());
    a.set(0,1);

    return 0;
}
#include "IntArray.h"

IntArray::IntArray(int len)
{
    m_pointer = new int[len];

    for(int i=0; i<len; i++)
    {
        m_pointer[i] = 0;
    }

    m_length = len;
}

IntArray::IntArray(const IntArray& obj)
{
    m_length = obj.m_length;

    m_pointer = new int[obj.m_length];

    for(int i=0; i<obj.m_length; i++)
    {
        m_pointer[i] = obj.m_pointer[i];
    }
}

int IntArray::length()
{
    return m_length;
}

bool IntArray::get(int index, int& value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        value = m_pointer[index];
    }

    return ret;
}

bool IntArray::set(int index, int value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        m_pointer[index] = value;
    }

    return ret;
}

IntArray::~IntArray()
{
    delete[]m_pointer;
}

工程开发中的构造过程可分为
资源无关的初始化操作
不可能出现异常情况的操作
需要使用系统资源的操作
可能出现异常情况,如:内存申请,访问文件

二阶构造示例一

class twophasecons
{
private:
twophasecons()
{
//第一阶段构造函数
}
bool construct()
{
return true;    //第二阶构函数
}
public:
static twophasecons *newinstance();   //对象创建函数
};

二阶构造示例二
TwoPhaseCons* TwoPhaseCons::NewInstance()
{
TwoPhaseCons *ret=new TwoPhaseCons();
//若二阶构造失败,返回 NULL
if(!(ret&&ret->construct()))
{
delete ret;
ret=NULL;
}
}

例 3 二阶构造初探

#include <stdio.h>

class TwoPhaseCons 
{
private:
    TwoPhaseCons() // 第一阶段构造函数
    {   
    }
    bool construct() // 第二阶段构造函数
    { 
        return true; 
    }
public:
    static TwoPhaseCons* NewInstance(); // 对象创建函数
};

TwoPhaseCons* TwoPhaseCons::NewInstance() 
{
    TwoPhaseCons* ret = new TwoPhaseCons();

    // 若第二阶段构造失败,返回 NULL    
    if( !(ret && ret->construct()) ) 
    {
        delete ret;
        ret = NULL;
    }

    return ret;
}


int main()
{
    TwoPhaseCons* obj = TwoPhaseCons::NewInstance();

    printf("obj = %p\n", obj);

    delete obj;

    return 0;
}

例 4 数组类的加强

#ifndef _INTARRAY_H_
#define _INTARRAY_H_

class IntArray
{
private:
    int m_length;
    int* m_pointer;

    IntArray(int len);
    IntArray(const IntArray& obj);
    bool construct();
public:
    static IntArray* NewInstance(int length); 
    int length();
    bool get(int index, int& value);
    bool set(int index ,int value);
    ~IntArray();
};

#endif
#include "IntArray.h"

IntArray::IntArray(int len)
{
    m_length = len;
}

bool IntArray::construct()
{
    bool ret = true;

    m_pointer = new int[m_length];

    if( m_pointer )
    {
        for(int i=0; i<m_length; i++)
        {
            m_pointer[i] = 0;
        }
    }
    else
    {
        ret = false;
    }

    return ret;
}

IntArray* IntArray::NewInstance(int length) 
{
    IntArray* ret = new IntArray(length);

    if( !(ret && ret->construct()) ) 
    {
        delete ret;
        ret = 0;
    }

    return ret;
}

int IntArray::length()
{
    return m_length;
}

bool IntArray::get(int index, int& value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        value = m_pointer[index];
    }

    return ret;
}

bool IntArray::set(int index, int value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        m_pointer[index] = value;
    }

    return ret;
}

IntArray::~IntArray()
{
    delete[]m_pointer;
}
#include <stdio.h>
#include "IntArray.h"

int main()
{
    IntArray* a = IntArray::NewInstance(5);    

    printf("a.length = %d\n", a->length());

    a->set(0, 1);

    for(int i=0; i<a->length(); i++)
    {
        int v = 0;

        a->get(i, v);

        printf("a[%d] = %d\n", i, v);
    }

    delete a;

    return 0;
}

小结:
构造函数只能决定对象的初始化状态
构造函数中初始化操作的失败不影响对象的诞生
初始化不完全的半成品对象是 bug 的重要来源
二阶构造人为的将初始化过程分为两部分
二阶构造能够确保创建的对象都是完整初始化的

第 28 课 友元的尴尬能力

什么是友元?
友元是 C++中的有一种关系
友元关系发生在函数与类之间或者类与类之间
友元关系式单项的,不能传递

友元的用法
在类中以 friend 关键字声明友元
类的友元可以是其他类或者具体函数
友元不是类的一部分
友元不受类中访问级别的限制
友元可以直接访问具体类的所有成员

在类中用 friend 关键字对函数或类进行声明
class point
{
double x;
double y;
friend void func(point& p);
};
void func(point& p)
{
}

例 1 友元的使用初探

#include <stdio.h>
#include <math.h>

class Point
{
    double x;
    double y;
public:
    Point(double x, double y)
    {
        this->x = x;
        this->y = y;
    }

    double getX()
    {
        return x;
    }

    double getY()
    {
        return y;
    }

    friend double func(Point& p1, Point& p2);
};

double func(Point& p1, Point& p2)
{
    double ret = 0;

    ret = (p2.y - p1.y) * (p2.y - p1.y) +
          (p2.x - p1.x) * (p2.x - p1.x);

    ret = sqrt(ret);

    return ret;
}

int main()
{
    Point p1(1, 2);
    Point p2(10, 20);

    printf("p1(%f, %f)\n", p1.getX(), p1.getY());
    printf("p2(%f, %f)\n", p2.getX(), p2.getY());
    printf("|(p1, p2)| = %f\n", func(p1, p2));


    return 0;
}

友元的尴尬
友元是为了兼容 C 语言的高效而诞生的
友元直接破坏了面向对象的封装性
友元在实际产品中的高效是得不偿失的
友元在现代软件工程中已经逐渐被遗弃

注意事项
友元关系不具备传递性
类的友元可以是其他类的成员函数
类的友元可以是某个完整的类
所有的成员函数都是友元

例 2 友元的深入分析

#include <stdio.h>

class ClassC
{
    const char* n;
public:
    ClassC(const char* n)
    {
        this->n = n;
    }

    friend class ClassB;
};

class ClassB
{
    const char* n;
public:
    ClassB(const char* n)
    {
        this->n = n;
    }

    void getClassCName(ClassC& c)
    {
        printf("c.n = %s\n", c.n);
    }

    friend class ClassA;
};

class ClassA
{
    const char* n;
public:
    ClassA(const char* n)
    {
        this->n = n;
    }

    void getClassBName(ClassB& b)
    {
        printf("b.n = %s\n", b.n);
    }
    /*
    void getClassCName(ClassC& c)
    {
        printf("c.n = %s\n", c.n);
    }
    */
};

int main()
{
    ClassA A("A");
    ClassB B("B");
    ClassC C("C");

    A.getClassBName(B);
    B.getClassCName(C);

    return 0;
}

小结:
友元为了兼顾 c 语言的高效而诞生的
友元直接破坏了面向对象的封装性
友元关系不具备传递性
类的友元可以是其他类的成员函数
类的友元可以是某个完整的类

第 29 课 类中的函数重载

函数重载的本质为相互独立的不同函数
C++中通过函数名和函数参数确定函数调用
无法直接通过函数名得到重载函数的入口地址
函数重载必然发生在同一个作用域中

类中的成员函数可以进行重载
构造函数的重载
普通成员函数的重载
静态成员函数的承载
问题:
全局函数,普通成员函数以及静态成员函数之间是否构成重载?

重载函数的本质为多个不同的函数
函数名和参数列表是唯一的标识
函数重载必须发生在同一个作用域中

例 1 类与重载全面分析

#include <stdio.h>

class Test
{
    int i;
public:
    Test()
    {
        printf("Test::Test()\n");
        this->i = 0;
    }

    Test(int i)
    {
        printf("Test::Test(int i)\n");
        this->i = i;
    }

    Test(const Test& obj)
    {
        printf("Test(const Test& obj)\n");
        this->i = obj.i;
    }

    static void func()
    {
        printf("void Test::func()\n");
    }

    void func(int i)
    {
        printf("void Test::func(int i), i = %d\n", i);
    }

    int getI()
    {
        return i;
    }
};

void func()
{
    printf("void func()\n");
}

void func(int i)
{
    printf("void func(int i), i = %d\n", i);
}

int main()
{
    func();
    func(1);

    Test t;        // Test::Test()
    Test t1(1);    // Test::Test(int i)
    Test t2(t1);   // Test(const Test& obj)

    func();        // void func()
    Test::func();  // void Test::func()

    func(2);       // void func(int i), i = 2;
    t1.func(2);    // void Test::func(int i), i = 2
    t1.func();     // void Test::func()

    return 0;
}

重载的意义
通过函数名对函数功能进行提示
通过参数列表对函数用法进行提示
扩展系统中已经存在的函数功能

例 2 重载的意义分析

#include <stdio.h>
#include <string.h>

char* strcpy(char* buf, const char* str, unsigned int n)
{
    return strncpy(buf, str, n);
}

int main()
{
    const char* s = "D.T.Software";
    char buf[8] = {0};

    //strcpy(buf, s);
    strcpy(buf, s, sizeof(buf)-1);

    printf("%s\n", buf);

    return 0;
}

重载能够扩展系统中已经存在的函数功能
那么重载能否也能够扩展其他更多的功能?

思考
下面的复数解决方案是否可行?
class Complex
{
public:
int a;
int b;
};
int main()
{
Complex c1={1,2};
Complex c2={3,4};
Complex c3=c1+c2;
return 0;
}

小结:
类的成员函数之间可以进行重载
重载必须发生在同一个作用域中
全局函数和成员函数不能构成重载关系
重载的意义在于扩展已经存在的功能

第 30 课 操作符重载的概念

下面的复数解决方案是否可行?
class Complex
{
public:
int a;
int b;
};
int main()
{
Complex c1={1,2};
Complex c2={3,4};
Complex c3=c1+c2;
return 0;
}

例 1 复数的加法操作

#include <stdio.h>

class Complex 
{
    int a;
    int b;
public:
    Complex(int a = 0, int b = 0)
    {
        this->a = a;
        this->b = b;
    }

    int getA()
    {
        return a;
    }

    int getB()
    {
        return b;
    }

    friend Complex Add(const Complex& p1, const Complex& p2);
};

Complex Add(const Complex& p1, const Complex& p2)
{
    Complex ret;

    ret.a = p1.a + p2.a;
    ret.b = p1.b + p2.b;

    return ret;
}

int main()
{

    Complex c1(1, 2);
    Complex c2(3, 4);
    Complex c3 = Add(c1, c2); // c1 + c2

    printf("c3.a = %d, c3.b = %d\n", c3.getA(), c3.getB());

    return 0;
}

Add 函数可以解决 complex 对象相加的问题,但是 complex 是现实世界中确实存在复数,并
且复数在数学中的地位和普通的实数相同。
为什么不能让+操作符也支持复数相加呢?

C++中的重载能够扩展操作符的功能
操作符的重载以函数的方式进行
本质:用特殊的函数扩展操作符的功能

通过 operator 关键字可以定义特殊的函数
operator 的本质是通过函数重载操作符
语法
type operator sign(const type p1,const type p2)
{
type ret;
return ret;
}
sign 为系统中预定义的操作符,如:+,-,*,/等

例 2 操作符重载初探

#include <stdio.h>

class Complex 
{
    int a;
    int b;
public:
    Complex(int a = 0, int b = 0)
    {
        this->a = a;
        this->b = b;
    }

    int getA()
    {
        return a;
    }

    int getB()
    {
        return b;
    }

    friend Complex operator + (const Complex& p1, const Complex& p2);
};

Complex operator + (const Complex& p1, const Complex& p2)
{
    Complex ret;

    ret.a = p1.a + p2.a;
    ret.b = p1.b + p2.b;

    return ret;
}

int main()
{

    Complex c1(1, 2);
    Complex c2(3, 4);
    Complex c3 = c1 + c2; // operator + (c1, c2)

    printf("c3.a = %d, c3.b = %d\n", c3.getA(), c3.getB());

    return 0;
}

可以将操作符重载函数定义为类的成员函数
比全局操作符重载少一个参数(左操作数)
不需要依赖友元就可以完成操作符重载
编译器优先在成员函数中寻找操作符重载函数
class Type
{
public:
Type operator Sign(const Type& p)
{
Type ret;
return ret;
}
};

例 3 成员函数重载操作符

#include <stdio.h>

class Complex 
{
    int a;
    int b;
public:
    Complex(int a = 0, int b = 0)
    {
        this->a = a;
        this->b = b;
    }

    int getA()
    {
        return a;
    }

    int getB()
    {
        return b;
    }

    Complex operator + (const Complex& p)
    {
        Complex ret;
        printf("Complex operator + (const Complex& p)\n");
        ret.a = this->a + p.a;
        ret.b = this->b + p.b;

        return ret;
    }

    friend Complex operator + (const Complex& p1, const Complex& p2);
};

Complex operator + (const Complex& p1, const Complex& p2)
{
    Complex ret;
    printf("Complex operator + (const Complex& p1, const Complex& p2)\n");
    ret.a = p1.a + p2.a;
    ret.b = p1.b + p2.b;

    return ret;
}

int main()
{

    Complex c1(1, 2);
    Complex c2(3, 4);
    Complex c3 = c1 + c2; // c1.operator + (c2)

    printf("c3.a = %d, c3.b = %d\n", c3.getA(), c3.getB());

    return 0;
}

小结:
操作符重载是 C++的强大特性之一
操作符重载的本质是通过函数扩展操作符的功能
operator 关键字是实现操作符重载的关键
操作符重载遵循相同的函数重载规则
全局函数和成员函数都可以实现对操作符的重载

第 31 课 完善的复数类

复数类应该具有的操作
运算:+,-,*,/
比较:==,!=
赋值:=
求模:modulus

利用操作符重载
统一复数与实数的运算方式
统一复数与实数的比较方式
Complex operator + (const Complex& c);
Complex operator - (const Complex& c);
Complex operator * (const Complex& c);
Complex operator / (const Complex& c);
bool operator == (const Complex& c);
bool operator != (const Complex& c);
Complex& operator = (const Complex& c);

例 1 复数类的实现


#ifndef _COMPLEX_H_
#define _COMPLEX_H_

class Complex
{
    double a;
    double b;
public:
    Complex(double a = 0, double b = 0);
    double getA();
    double getB();
    double getModulus();

    Complex operator + (const Complex& c);
    Complex operator - (const Complex& c);
    Complex operator * (const Complex& c);
    Complex operator / (const Complex& c);

    bool operator == (const Complex& c);
    bool operator != (const Complex& c);

    Complex& operator = (const Complex& c);
};

#endif
#include "Complex.h"
#include "math.h"

Complex::Complex(double a, double b)
{
    this->a = a;
    this->b = b;
}

double Complex::getA()
{
    return a;
}

double Complex::getB()
{
    return b;
}

double Complex::getModulus()
{
    return sqrt(a * a + b * b);
}

Complex Complex::operator + (const Complex& c)
{
    double na = a + c.a;
    double nb = b + c.b;
    Complex ret(na, nb);

    return ret;
}

Complex Complex::operator - (const Complex& c)
{
    double na = a - c.a;
    double nb = b - c.b;
    Complex ret(na, nb);

    return ret;
}

Complex Complex::operator * (const Complex& c)
{
    double na = a * c.a - b * c.b;
    double nb = a * c.b + b * c.a;
    Complex ret(na, nb);

    return ret;
}

Complex Complex::operator / (const Complex& c)
{
    double cm = c.a * c.a + c.b * c.b;
    double na = (a * c.a + b * c.b) / cm;
    double nb = (b * c.a - a * c.b) / cm;
    Complex ret(na, nb);

    return ret;
}

bool Complex::operator == (const Complex& c)
{
    return (a == c.a) && (b == c.b);
}

bool Complex::operator != (const Complex& c)
{
    return !(*this == c);
}

Complex& Complex::operator = (const Complex& c)
{
    if( this != &c )
    {
        a = c.a;
        b = c.b;
    }

    return *this;
}
#include <stdio.h>
#include "Complex.h"

int main()
{
    Complex c1(1, 2);
    Complex c2(3, 6);
    Complex c3 = c2 - c1;
    Complex c4 = c1 * c3;
    Complex c5 = c2 / c1;

    printf("c3.a = %f, c3.b = %f\n", c3.getA(), c3.getB());
    printf("c4.a = %f, c4.b = %f\n", c4.getA(), c4.getB());
    printf("c5.a = %f, c5.b = %f\n", c5.getA(), c5.getB());

    Complex c6(2, 4);

    printf("c3 == c6 : %d\n", c3 == c6);
    printf("c3 != c4 : %d\n", c3 != c4);

    (c3 = c2) = c1;

    printf("c1.a = %f, c1.b = %f\n", c1.getA(), c1.getB());
    printf("c2.a = %f, c2.b = %f\n", c2.getA(), c2.getB());
    printf("c3.a = %f, c3.b = %f\n", c3.getA(), c3.getB());

    return 0;
}

C++规定赋值操作符(=)只能重载为成员函数
操作符重载不能改变原操作符的优先级
操作符重载不能改变操作数的个数
操作符重载不应该改变操作符的原有语义

小结:
复数的概念可以通过自定义类实现
复数中运算操作可以通过操作符重载实现
赋值操作符只能通过成员函数实现
操作符重载的本质为函数定义

第 32 课 初探 C++标准库

操作符<<的原生意义是按位左移,例:1<<2
其意义是将整数 1 按位左移 2 位,即:0000 0001 0000 0100
重载左移操作符,将变量或常量左移到一个对象中

例 1 重载左移操作符

#include <stdio.h>

const char endl = '\n';

class Console
{
public:
    Console& operator << (int i)
    {
        printf("%d", i);

        return *this;
    }
    Console& operator << (char c)
    {
        printf("%c", c);

        return *this;
    }
    Console& operator << (const char* s)
    {
        printf("%s", s);

        return *this;
    }
    Console& operator << (double d)
    {
        printf("%f", d);

        return *this;
    }
};

Console cout;

int main()
{
    cout << 1 << endl;
    cout << "D.T.Software" << endl;

    double a = 0.1;
    double b = 0.2;

    cout << a + b << endl;

    return 0;
}

重复发明轮子并不是一件有创造性的事,站在巨人的肩膀上解决问题会更加有效!

C++标准库并不是 C++语言的一部分
C++标准库是由类库和函数库组成的集合
C++标准库中定义的类和对象都位于 std 命名空间中
C++标准库的头文件都不带.h 后缀
C++标准库涵盖了 c 库的功能

例 2 c++标准库的 c 库兼容

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>

using namespace std;


int main()
{
    printf("Hello world!\n");

    char* p = (char*)malloc(16);

    strcpy(p, "D.T.Software");

    double a = 3;
    double b = 4;
    double c = sqrt(a * a + b * b);

    printf("c = %f\n", c);

    free(p);

    return 0;
}

例 3 c++中的输入输出

#include <iostream>
#include <cmath>

using namespace std;


int main()
{
    cout << "Hello world!" << endl;

    double a = 0;
    double b = 0;

    cout << "Input a: ";
    cin >> a;

    cout << "Input b: ";
    cin >> b;

    double c = sqrt(a * a + b * b);

    cout << "c = " << c << endl;

    return 0;
}

小结:
c++标准库是由类库和函数库组成的集合
c++标准库包含经典算法和数据结构的实现
c++标准库涵盖了 c 库的功能
c++标准库位于 std 命名空间中

第 33 课 C++中的字符串类

C 语言不支持真正意义上的字符串
C 语言用字符串数组和一组函数实现字符串操作
C 语言不支持自定义类型,因此无法获得字符串类型

从 c 到 c++的进化过程引入了自定义类型
在 c++中可以通过完成字符串类型的定义
问题:c++中的原生类型系统是否包含字符串类型

C++语言直接支持 C 语言的所有概念
C++语言中没有原生的字符串类型
C++标准库提供了 string 类型
string 直接支持字符串连接
string 直接支持字符串的大小比较
string 直接支持字符串查找和提取
string 直接支持字符串的插入和替换

例 1 字符串类的使用

#include <iostream>
#include <string>

using namespace std;

void string_sort(string a[], int len)
{
    for(int i=0; i<len; i++)
    {
        for(int j=i; j<len; j++)
        {
            if( a[i] > a[j] )
            {
                swap(a[i], a[j]);
            }
        }
    }
}

string string_add(string a[], int len)
{
    string ret = "";

    for(int i=0; i<len; i++)
    {
        ret += a[i] + "; ";
    }

    return ret;
}

int main()
{
    string sa[7] = 
    {
        "Hello World",
        "D.T.Software",
        "C#",
        "Java",
        "C++",
        "Python",
        "TypeScript"
    };

    string_sort(sa, 7);

    for(int i=0; i<7; i++)
    {
        cout << sa[i] << endl;
    }

    cout << endl;

    cout << string_add(sa, 7) << endl;

    return 0;
}

字符串与数字的转换
标准库中提供了相的类对字符串和数字进行转换
字符串流类(sstream)用于 string 的转换
相关头文件
istringstream 字符串输入流
ostringstream 字符串输出流

使用方法
String 数字
istringstrean iss(“123.45”);
double num;
iss>>num;
数字string
ostringstream oss;
oss<<543.21;
string s=oss.str();

例 2 字符串和数组的转换

#include <iostream>
#include <sstream>
#include <string>

using namespace std;


#define TO_NUMBER(s, n) (istringstream(s) >> n)
#define TO_STRING(n) (((ostringstream&)(ostringstream() << n)).str())
/*bool to_number(const string& s,int& n)
{
    istringstream iss(s);
    return iss>>n;
}*/
/*
string to_string(int n)
{
    ostringstream oss;
    osss<<n;
    return oss.str();
}*/
int main()
{

    double n = 0;

    if( TO_NUMBER("234.567", n) )
    {
        cout << n << endl;    
    }

    string s = TO_STRING(12345);

    cout << s << endl;     

    return 0;
}

字符串循环右移
示例:abcdefg 循环右移 3 位后得到 efgabcd

例 3 用 c++完成面试题

#include <iostream>
#include <string>

using namespace std;

string operator >> (const string& s, unsigned int n)
{
    string ret = "";
    unsigned int pos = 0;

    n = n % s.length();
    pos = s.length() - n;
    ret = s.substr(pos);
    ret += s.substr(0, pos);
    //abcdefg==>8
    //abcdefg==>1
    //8%7==>1
    //7-1==>6
    //abcedef g
    //ret=>g
    //ret==>g+abcdef
    //ret=gabcdef

    return ret;
}

int main()
{
    string s = "abcdefg";
    string r = (s >> 3);

    cout << r << endl;

    return 0;
}

小结
应用开发中大多数情况都进行字符串处理
C++中没有直接支持原生的字符串类型
标准库中通过 string 类支持字符串的概念
string 类支持字符串和数字的相互转换
string 类的应用使得问题的求解变得简单

字符串反转
要求:
使用 string 类完成
示例:
“we;tonight;you” ”ew;thginot;uoy”
提示:
string 类提供了成员函数可以查找目标字符的位置

```
#include <iostream>
#include <string>

using namespace std;

string reverse(const string& s, const char c)
{
    string ret = "";

    return ret;
}

int main()
{
    cout << reverse("", ';') << endl;                 // 输出:空字符串
    cout << reverse(";", ';') << endl;                // 输出:;
    cout << reverse("abcde;", ';') << endl;           // 输出:edcba;
    cout << reverse("we;tonight;you", ';') << endl;   // 输出:ew;thginot;uoy

    return 0;
}
34 课  数组操作符的重载
问题:
string 类对象还具备 c 方式字符串的灵活性吗?还能直接访问单个字符吗?

字符串类的兼容性
string 类最大限度的考虑了 c 字符串的兼容性
可以按照使用 c 字符串的方式使用 string 对象
string s=”a1b2c3d4e”;
int n=0;
for(int i=0;i<s.length();i++)
{
if(isdigit(s[i]))
{
n++;
}
}

例 1  用 c 方式使用 string

include

include

using namespace std;

int main()
{
string s = “a1b2c3d4e”;
int n = 0;

for(int i = 0; i<s.length(); i++)
{
    if( isdigit(s[i]) )
    {
        n++;
    }
}

cout << n << endl;

return 0;

}


类的对象怎么支持数组的下标访问?

被忽略的事实
数组访问符是 c/c++中的内置操作符
数组访问符的原生意义是数组访问和指针运算
a[n]*(a+n)*(n+a)n[a]

例 2  指针与数组的复习

include

include

using namespace std;

int main()
{
int a[5] = {0};

for(int i=0; i<5; i++)
{
    a[i] = i;
}

for(int i=0; i<5; i++)
{
    cout << *(a + i) << endl;    // cout << a[i] << endl;
}

cout << endl;

for(int i=0; i<5; i++)
{
    i[a] = i + 10;    // a[i] = i + 10;
}

for(int i=0; i<5; i++)
{
    cout << *(i + a) << endl;  // cout << a[i] << endl;
}

return 0;

}


数组访问操作符([])
只能通过类的成员函数重载
重载函数能且仅能使用一个参数
可以定义不同参数的多个重载函数

例 3  重载数组访问操作符

include

include

using namespace std;

class Test
{
int a[5];
public:
int& operator [] (int i)
{
return a[i];
}

int& operator [] (const string& s)
{
    if( s == "1st" )
    {
        return a[0];
    }
    else if( s == "2nd" )
    {
        return a[1];
    }
    else if( s == "3rd" )
    {
        return a[2];
    }
    else if( s == "4th" )
    {
        return a[3];
    }
    else if( s == "5th" )
    {
        return a[4];
    }

    return a[0];
}

int length()
{
    return 5;
}

};

int main()
{
Test t;

for(int i=0; i<t.length(); i++)
{
    t[i] = i;
}

for(int i=0; i<t.length(); i++)
{
    cout << t[i] << endl;
}

cout << t["5th"] << endl;
cout << t["4th"] << endl;
cout << t["3rd"] << endl;
cout << t["2nd"] << endl;
cout << t["1st"] << endl;

return 0;

}


例 4  数组类的完善
#ifndef _INTARRAY_H_
#define _INTARRAY_H_

class IntArray
{
private:
    int m_length;
    int* m_pointer;

    IntArray(int len);
    IntArray(const IntArray& obj);
    bool construct();
public:
    static IntArray* NewInstance(int length); 
    int length();
    bool get(int index, int& value);
    bool set(int index ,int value);
    int& operator [] (int index);
    IntArray& self();
    ~IntArray();
};

#endif
#include "IntArray.h"

IntArray::IntArray(int len)
{
    m_length = len;
}

bool IntArray::construct()
{
    bool ret = true;

    m_pointer = new int[m_length];

    if( m_pointer )
    {
        for(int i=0; i<m_length; i++)
        {
            m_pointer[i] = 0;
        }
    }
    else
    {
        ret = false;
    }

    return ret;
}

IntArray* IntArray::NewInstance(int length) 
{
    IntArray* ret = new IntArray(length);

    if( !(ret && ret->construct()) ) 
    {
        delete ret;
        ret = 0;
    }

    return ret;
}

int IntArray::length()
{
    return m_length;
}

bool IntArray::get(int index, int& value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        value = m_pointer[index];
    }

    return ret;
}

bool IntArray::set(int index, int value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        m_pointer[index] = value;
    }

    return ret;
}

int& IntArray::operator [] (int index)
{
    return m_pointer[index];
}

IntArray& IntArray::self()
{
    return *this;
}

IntArray::~IntArray()
{
    delete[]m_pointer;
}
#include <iostream>
#include <string>
#include "IntArray.h"

using namespace std;

int main()
{
    IntArray* a = IntArray::NewInstance(5);    

    if( a != NULL )
    {
        IntArray& array = a->self();

        cout << "array.length() = " << array.length() << endl;

        array[0] = 1;

        for(int i=0; i<array.length(); i++)
        {  
            cout << array[i] << endl;
        }
    }

    delete a;

    return 0;
}

小结:
string 类最大程度的兼容了 c 字符传递的用法
数组访问符的重载能够使得对象模拟数组的行为
只能通过类的成员函数重载数组访问符
重载函数能且仅能使用一个参数

第 35 课 函数对象分析

编写一个函数
函数可以获得菲波那切数列每项的值
每调用一次返回一个值
函数可根据需要重复使用
for(int i=0;i<10;i++)
{
cout<

#include <iostream>
#include <string>

using namespace std;

int fib()
{
    static int a0 = 0;
    static int a1 = 1;

    int ret = a1;

    a1 = a0 + a1;
    a0 = ret;

    return ret;
}


int main()
{
    for(int i=0; i<10; i++)
    {
        cout << fib() << endl;
    }

    cout << endl;

    for(int i=0; i<5; i++)
    {
        cout << fib() << endl;
    }

    return 0;
}

函数一旦开始调用就无法重来
静态局部变量处于函数内部,外界无法改变
函数为全局函数,是唯一的,无法多次独立使用
无法指定某个具体的数列项作为初始值

函数对象
使用具体的类对象取代函数
该类的对象具有函数调用的行为
构造函数指定具体数列的起始位置
多个对象相互独立的求解数列项

函数调用操作符(())
只能通过类的成员函数重载
可以定义不同参数的多个重载函数

例 2 最终解决方案

#include <iostream>
#include <string>

using namespace std;

class Fib
{
    int a0;
    int a1;
public:
    Fib()
    {
        a0 = 0;
        a1 = 1;
    }

    Fib(int n)
    {
        a0 = 0;
        a1 = 1;

        for(int i=2; i<=n; i++)
        {
            int t = a1;

            a1 = a0 + a1;
            a0 = t;
        }
    }

    int operator () ()
    {
        int ret = a1;

        a1 = a0 + a1;
        a0 = ret;

        return ret;
    }
};

int main()
{
    Fib fib;

    for(int i=0; i<10; i++)
    {
        cout << fib() << endl;
    }

    cout << endl;

    for(int i=0; i<5; i++)
    {
        cout << fib() << endl;
    }

    cout << endl;

    Fib fib2(10);

    for(int i=0; i<5; i++)
    {
        cout << fib2() << endl;
    }

    return 0;
}

小结:
函数调用操作符(())是可重载的
函数调用操作符只能通过类的成员函数重载
函数调用操作符可以定义不同参数的多个重载函数
函数对象用于工程中取代函数指针

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

phial03

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值