Standard C++ Episode 1

从C到C++
计算机语言的发展

算盘  -  面向硬件的语言,按照珠算口诀拨动算珠。

电子计算机
 - 机器语言编程。各种101011101...
 - 汇编语言的编程。各种ADD、MOV...
 - 高级语言的编程初级阶段。例如Fortran,像汇编一样没有避免各类跳转使用,各种流程转向,难于维护。
 - 面向过程结构化高级语言。面向过程(解决问题的过程,按照步骤一步一步解决问题的过程)通过顺序结构、分支结构、循环结构、函数等来实现结构化程序设计。
 - 面向对象程序设计。计算机处理的对象是数据信息。面向对象程序设计实现了面向被处理的事物(数据)对象。
 - 面向问题的程序设计。

 

 

 

关于C/C++
1973年 Dennis M. Ritchie, C语言, 用C语言重新实现了UNIX内核。
1978年 Dennis M. Ritchie, 出版《The C Programming Language》, 成为了C语言事实标准。
1989年 ANSI C          C89
1990年  ISO C          C90
1999年  ISO C 修订C89, C99
197X年 Bajarne Stroustrup, simula是早期的面向对象语言,用于仿真,性能低下。
1979年 Bajarne Stroustrup, 来到贝尔实验室做多核UNIX系统仿真。过程中通过扩展宏为C语言增加了类似simula的面向对象机制,称为Cpre。进一步实现了C with Class, 吸收了simula里类概念,吸收了Alogo 68的操作符重载,吸收了Ada里面的模板、命名空间的概念,还吸收了Smalltalk的引用、异常。
1983年 Bajarne Stroustrup正式命名C++
1985年 CFront 1.0第一款商用C++编译器
1987年 GNU C++编译器诞生
1990年 Borland C++
1992年 Microsoft C++, IBM C++
1998年 ISO C++98。这个C++标准促成了1999年ISO C修订C89。
2003年 ISO C++03。
2011年 ISO C++2011(C++11/C++0X)

 

 

 

思考一个问题的解决方案,宏观上面向对象,微观面向过程。对于C需要人工将对象层面的描述转为过程层面的描述,才能C语言的实现。对于C++,C++支持面向对象,将问题域和方法域统一,我们可以怎么思考就怎么用C++描述,C++直接实现。
C++的特别之处在于,但是不限于:
    C++支持面向对象,将问题域和方法域统一。
    C++支持泛型编程。
    C++支持异常机制。
    C++支持重载。

 

 

 

C++编译器 GNU GCC
(1) g++,或者gcc -lstdc++(-lstdc++选项指明GCC须要使用标准c++的运行库)
(2)C++源程序文件扩展名可以是*.cpp/*.cc/*.C/*.cxx/*.c++

从C到C++是一种改造和扩充,C++对C的基本改造包括但不限于:
命名空间
结构体枚举和联合
布尔类型
操作符 分隔符别名
函数(重载、形式参数缺省值、哑元形式参数、内联)
动态内存分配new/delete
引用
显式类型转换

 

-------------------------------------------分割线来了-----------------------------------------------------

一、namespace(命名空间)。

1、对程序中的标识符按照某种逻辑划分成若干组,分组可以一层又一层进行, 每一组叫做一个命名空间。
2、定义明命名空间的方法:
    namespace 命名空间名称 {
        //命名空间成员
}
3、使用命名空间
(1)操作符"::"称为作用域限定符。用法:
            名字空间名::名字空间成员
                表示访问特定名字空间中的特定成员。
(2)using namespace 是应用某名字空间的指令, 他使得指定命名空间对using
namespace之后执行的代码可见。也就是说using namespace 修改了可见性。用法:
            using namespace 名字空间名称;
在该条指令之后的代码对指令所指名字空间中的所有成员都可见,可直接访问这些成员,无须加名字空间名称和作用域限定符"::"。
(3)名字空间声明。用法:
    using 名字空间名称::名字空间成员;
这条语句修改了指定标识符的作用域。此语句将直接把指定名字空间中的某个成员引入当前作用域,可直接访问这些成员,无需加作用域限定符"::"
(4) 匿名名字空间。
如果一个标识符没有被显式地定义在任何名字空间中,编译器会将其缺省地置为匿名名字空间中。对于匿名名字空间中的成员可以直接通过"::"来访问,例如"::名字空间成员"。
(5) 名字空间合并。同命命名空间是同一个命名空间。如果文件一里面的namespace ns1 {}和文件二里面的namespace ns2 {},如果同时参与编译,那么编译器会将两个命名空间成员合并(取并集),同名命名空间是同一个命名空间。
(6) 名字空间嵌套。
例如嵌套的命名空间如下:
namespace ns1 {
    namespace ns2 {
        namespace ns3 {
            foo();
        }
    }
}

应用该命名空间的语句可以是:
using namespaces ns1::ns2::ns3;
ns1::ns2::ns3::foo();

 

 

二、C++中的结构、联合和枚举与ANSI C中的相比,C++中:
1、结构体。
(1) 定义结构体型变量时候,可以省略struct关键字。
(2) 结构体内部可以定义函数,称为成员函数。
    PS: C++中结构体能实现的功能,通过class(类)完全可以实现。事实上,从C++的结构体内部可以有成员函数就已经说明c++的结构体已经被class替代了,但是为了兼容ANSI C, C++还是保留了这个关键字。

(3) C++中sizeof(空结构体) 结果是1byte.这更符合逻辑。ANSI C中sizeof(空结构体)结果是0byte.

    /*C++*/
    #include <iostream>
    main()
    {
        struct s1 {};
        s1 ss;
        std::cout << "sizeof(struct s1)是" << sizeof(struct s1) << std::endl;
        std::cout << "sizeof(ss)是" << sizeof(ss) << std::endl;
        std::cout << "&ss是" << &ss << std::endl;
            //打印结果是
            //sizeof(struct s1)是1
            //sizeof(ss)是1
            //&ss是0x7ffffc7f0e3f
    }


    /*ANSI C*/
    #include <stdio.h>
    main()
    {
        struct s1 {};
        struct s1 ss;
        printf("sizeof(struct s1)是%d\n", sizeof(struct s1));
        printf("sizeof(ss)是%d\n", sizeof(ss));
        printf("&ss是%p\n", &ss);

        //打印结果是:
        //    sizeof(struct s1)是0
        //    sizeof(ss)是0
        //    &ss是0x7fff0e1e78f0
    }

/*
 * 以上代码及运行结果可以得出结论:
 * C语言不够严格。C语言中空结构体变量ss大小是0,说明没有占用存储空间;地址非空说明至少占用了一个字节的存储空间;自相矛盾。C++语言中作出了调整:C++中空结构体对象ss地址非空,大小是1。
 *
 */

 

2、联合
增加了匿名联合的概念。C++借用联合语法的形式,描述了一些变量在内存中的布局方式。

3、枚举
枚举是一个俄独立的数据类型。不是像C语言中enum实质就是int型。
C++的枚举更为严格。
enum e_enum1 {A, B, C};
e_enum1 e1;//变量e1只能取值为A、 B、 C三者之一,绝对不可以是其他值。
e1 = A;
e1 = B;
e1 = 0;//error
e1 = F;//error

 

三、C++中的布尔文字和布尔类型
布尔类型关键字bool
bool b_B;
b_B = true;//true和false称为布尔文字。
b_B = false;
b_B = 100;//此时编译阶段,编译器将右操作数作为逻辑表达式运算,运算结果为逻辑值。
b_B = 100.78f;
b_B = 0;


四、C++中的操作符、分割符别名
&& alis and
|| alis or
&  alis bitand
^  alis xor

{  alis <%
}  alis %>
[  alis <:
]  alis :>

 

五、C++中的函数
1、重载:在同一作用域中,函数名称相同,参数表不同的函数构成重载关系。C不允许重载。C++允许函数重载。
    注意:参数表不同可以是a:类型不同,b:顺序不同。

    C++支持函数重载,其原理在于C++编译器是在编译期间才确定了函数名称唯一。C++编译器会对程序中的函数作换名,将参数表中的类型信息汇合到新的 函数名称中,从而也就保证了函数名称的唯一性。换名是在编译器进行编译时完成的。linux系统中可以使用nm命令列出.o文件中的符号,以查看函数新换 的函数名称。
    另外,通过extern "C",可以要求编译器不做C++换名,以方便在C语言的模块中使用C++编译生成的机器代码。

2、缺省参数和哑元参数
(1)如果调用一个函数时候,没有提供实际参数,那么对于形式参数就取缺省发值。

(2)如果一个参数带有缺省值,那么他后边的所有参数都应该带缺省值。

(3)如果一个函数声明和定义分开,那么只有放在函数声明中的缺省值才真正生效。

(4)带有缺省参数的函数,应当特别注意避免重载歧义。

(5)只有类型而没有名字的形式参数,谓之"哑元"。"哑元"的出现完全是为了曲线完成函数区分,实现某种情况下的重载。


P.S.(post script):进程映射(memory map)简介
一个可执行程序被load后在内存中的布局叫做进程映像。下图是基本布局图:

 

 

3、内联。
(1)编译器用函数的二进制代码替换函数调用语句,以图减少函数调用的时间开销,这种优化策略叫做内联。内联增加了代码的存储空间。
(2)递归函数无法内联。
(3)频繁调用的简单函数适合内联,而稀少调用的复杂函数不适合内联。
(4)通过inline关键字,可以建议编译器对指定函数进行内联,但是仅仅是建议而已。最终是否实施内联优化策略取决于编译器。

 

 

六、C++动态内存分配

(1)ANSI C的动态内存分配同样可用
(2)new / delete 实现单个变量的存储空间申请和销毁
(3)new [] / delete[] 实现成倍存储单元申请和销毁
new/delete底层会调用malloc和free(), 并且在调用malloc和free的同时会做更多的工作。

 1 /*
 2  * new/delete new[]/delete[]操作符练习
 3  */
 4 #include <iostream>
 5 using namespace std;
 6 int main (void) {
 7 //    int* pi = (int*)malloc (sizeof (int));
 8 //    free (pi);
 9     int* pi = new int;
10     *pi = 1000;
11     cout << *pi << endl;
12     delete pi;
13     pi = NULL;
14     /*
15     *pi = 2000;
16     cout << *pi << endl;
17     */
18     pi = new int[10];
19     for (size_t i = 0; i < 10; ++i)
20         pi[i] = i;
21     for (size_t i = 0; i < 10; ++i)
22         cout << pi[i] << ' ';
23     cout << endl;
24     delete[] pi;
25     pi = NULL;
26     pi = new int (1234);
27     cout << *pi << endl;
28     delete pi;
29     pi = NULL;
30     char buf[4] = {0x12,0x34,0x56,0x78};
31     pi = new (buf) int;
32     cout << hex << *pi << endl;
33 //    delete pi;
34     cout << (void*)pi << ' ' << (void*)buf << endl;
35     int (*p)[4] = new int[3][4];
36     delete[] p;
37     int (*q)[4][5] = new int[3][4][5];
38     delete[] q;
39     return 0;
40 }

 

 

七、C++引用
1、引用即别名。C++源程序中我们通过引用给变量起别名,实质是在给变量所代表的存储区起别名。建立引用后,那么就可以用不同的标识符代表同一块存储区。例如:
int n_a;
int& n_b = n_a;
n_b = 10;
cout << n_a << endl;//n_a 是 10;
2、引用必须初始化。否则编译报错。
3、引用一旦初始化,不能再引用别的对象了。

引用的应用场景:
1、引用用在函数参数表中。
a:实现了修改实际参数
b:避免传递参数产生的数据拷贝。
c:必要的时候,可以通过const还可以防止在函数中意外的修改实际参数值。

2、引用函数返回值
(P.S.:C/C++中变量(对象)区分左值和由值。允许出现在"="左边的值叫做左值,允许出现在"="右边的值叫做右值。举例:一个普通变量,例如int a = 'A'; 中,a既有左值又有右值,'A'只有右值。)
引用函数返回值。从一个函数返回引用往往是为了将该函数的返回之作为左值使用。但是,一定要保证函数所返回的引用的目标在函数返回后依然有效。否则,将导致不确定的后果。
不要返回局部变量的引用,可以返回全局、静态、成员变量的引用,也可以返回引用型形式参数变量本身。

5、引用和指针
(1)引用的本质就是指针,很多场合下引用和指针可以互换。

int & func(){}
main() {
int b = 10;
int &a = b;//引用在编译器编译期间会被处理成匿名的指针常量(姑且认为是名为p_b, 形如int * const p_b = &b; 那么a就等价于*p_b)。这样很多场合引用和指针两种解决问题的方法。
int a = func(b);
func(b) = a;
}



(2)在C++层面上引用和指针存在以下不同:
    A.指针是一种数据类型,指针变量是实体变量,指针占有存储空间,但是引用不是一种数据类型,引用不是实体变量,不再占有存储空间。
    B.指针可以不初始化,但是引用必须初始化。
    C.指针可以一会儿指向变量a,可以一会儿指向变量b。指针的目标可以修改,但是引用的目标不能修改。
    D.不可以定义指向引用的指针。因为引用不是一种数据类型。举例 在int a; int& b = a;背景下的语句 int& *p = &b; 就是错的,是无法理解的。
    E.可以定义指针的引用,不能定义引用的引用。
    int a;
    int * p = &a;
    int * &q = p;//正确
    int & r = a;
    int&& s = r;//错误。引用只有一级,不可以定义引用的引用,引用关系不能传递。
    F.可以定义指针的数组,但是不能定义引用的数组。原因还在于 引用本身不是一种数据类型,引用是一种变量标识符之间的关系。
    int a, b, c;
    int *parr[] = {&a, &b, &c};//指针数组定义
    int& rarr[] = {a, b, c};//错误。无法理解

    G.可以定义数组的引用。
    int g_arr[8]={0};
    int (&grid)[8] = g_arr;//数组g_arr的引用

 

 1 /*
 2  *引用与指针练习
 3  */
 4 #include <iostream>
 5 using namespace std;
 6 void swap1 (int a, int b) {
 7     int c = a;
 8     a = b;
 9     b = c;
10 }
11 void swap2 (int* a, int* b) {
12     int c = *a;
13     *a = *b;
14     *b = c;
15 }
16 void swap3 (int& a, int& b) {
17     int c = a;
18     a = b;
19     b = c;
20 }
21 void swap4 (const char* x, const char* y) {
22     const char* z = x;
23     x = y;
24     y = z;
25 }
26 void swap5 (const char** x, const char** y) {
27     const char* z = *x;
28     *x = *y;
29     *y = z;
30 }
31 void swap6 (const char*& x, const char*& y) {
32     const char* z = x;
33     x = y;
34     y = z;
35 }
36 struct Student {
37     char name[1024];
38     int age;
39 };
40 void print (const struct Student& s) {
41     cout << s.name << "" << s.age << endl;
42 //    s.age = -1;
43 }
44 void foo (const int& x) {
45     cout << x << endl;
46 }
47 int main (void) {
48     int a = 100, b = 200;
49 //    swap1 (a, b);
50 //    swap2 (&a, &b);
51     swap3 (a, b);
52     cout << a << ' ' << b << endl; // 200 100
53     const char* x = "hello", *y = "world";
54 //    swap4 (x, y);
55 //    swap5 (&x, &y);
56     swap6 (x, y);
57     cout << x << ' ' << y << endl;
58     Student s = {"张飞", 22};
59     print (s);
60     print (s);
61     foo (100);
62     return 0;
63 }

 

 1 /*
 2  * 引用练习
 3  */
 4 #include <iostream>
 5 using namespace std;
 6 int g_data = 100;
 7 int& foo (void) {
 8     return g_data;
 9 }
10 int& bar (void) {
11     int a = 100;
12     return a;
13 }
14 int& fun (void) {
15     int b = 200;
16     return b;
17 }
18 int& hum (int& a) {
19     return a;
20 }
21 int main (void) {
22     int data = foo ();
23     cout << data << endl; // 100
24     foo () = 200;
25     cout << g_data << endl;
26     foo () += 100;
27     ++foo ();
28     cout << g_data << endl; // 301
29     int& a = bar ();
30     fun ();
31     cout << a << endl; // 200
32     return 0;
33 }

 

 

八、显式类型转换操作符
ANSI C语言显式类型转换是"目标类型变量 = (目标类型) 源类型变量",  而在C++中把显式类型转换操作符分为5类:

1、静态类型转换
static_cast<目标类型> (源类型变量)
    如果在目标类型和源类型之间某一个方向上可以作隐式类型转换,那么在两个方向上都可以作静态类型转换。反之如果在两个方向上都不能做隐式类型转换,那么在任意一个方向上也不能作静态类型转换。
    举例: 如果
    int * p1;
    void* p2;
    p2 = p1;//隐式类型转换。
    那么
    就可以作静态类型转换 static_cast<int*> (p2);


    如果存在从源类型到目标类型的自定义转换规则,那么也可以使用静态类型转换。

2、动态类型转换。
dynamic_cast<目标类型>(源类型变量)
    用在具有多态性的父子类指针或者引用之间。

3、常类型转换。给一个拥有const属性的指针或者引用去常属性。
const_cast<目标类型> (源类型变量)
举例:
int a = 100;
const int* p1 = &a;
*p1 = 200;//ERROR
int* p2 = p1;//ERROR
int *p2 = const_cast<int *>(p1);//OK
p2 = 200;//OK

 1 /*
 2  * const_cast<>显式类型转换操作符练习
 3  */
 4 #include <iostream>
 5 using namespace std;
 6 int main (void) {
 7     const volatile int a = 100;
 8 //    a = 200;
 9     const volatile int* p1 = &a;
10 //    *p1 = 200;
11     int* p2 = const_cast<int*> (p1);
12     *p2 = 200;
13     cout << *p2 << endl; // 200
14     cout << a << endl; // 200
15     // cout << 100 << endl;
16     return 0;
17 }

 

4、重解释类型转换。在不同类型的指针或者引用之间做类型转换。
reinterpret_cast<目标类型> (源类型变量)

 1 /*
 2  * 重解释类型转换操作符reinterpret_cast<>
 3  */
 4 #include <iostream>
 5 using namespace std;
 6 int main (void) {
 7     int i = 0x12345678;
 8     char* p = reinterpret_cast<char*> (&i);
 9     for (size_t i = 0; i < 4; ++i)
10         cout << hex << (int)p[i] << ' ';
11     cout << endl;
12     float* q = reinterpret_cast<float*> (&i);
13     cout << *q << endl;
14     void* v = reinterpret_cast<void*> (i);
15     cout << v << endl;
16     return 0;
17 }

 

5、目标类型变量 = 目标类型(源类型变量)
    举例:int a = int(3.14);

 


九、C++之父的建议
1、少用宏,多用 const, enum和inline

2、变量随用随声明,同时初始化。比如在for循环中定义循环变量i: for(int i = 0, i < N, ++i);

3、少用malloc/free, 多用new/delete

4、少用C风格的强制类型转换,多用C++提供的类型转换操作符号。

5、少用C风格的字符串,多用string。

6、要树立面向对象的编程思想。

(P.S.: hexdump命令可以以十六进制查看二进制文件, nm命令可以查看二进制文件的符号)

 


初学
C++ Primer Plus
进阶
C++ Primer
Effective C++
More Effective C++
深入
C++程序设计语言,Bjarena Stroustrup,机械版
深度探索C++对象模型
休闲
C++语言99个常见错误
C++语言的设计与演化,Bjarena Stroustrup
-----------------


转载于:https://www.cnblogs.com/libig/p/4741161.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值