博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Standard C++ Episode 1
阅读量:4709 次
发布时间:2019-06-10

本文共 10776 字,大约阅读时间需要 35 分钟。

从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 
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 
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 10053 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 
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; // 10024 foo () = 200;25 cout << g_data << endl;26 foo () += 100;27 ++foo ();28 cout << g_data << endl; // 30129 int& a = bar ();30 fun ();31 cout << a << endl; // 20032 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 
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
(p1);12 *p2 = 200;13 cout << *p2 << endl; // 20014 cout << a << endl; // 20015 // cout << 100 << endl;16 return 0;17 }

 

4、重解释类型转换。在不同类型的指针或者引用之间做类型转换。

reinterpret_cast<目标类型> (源类型变量)

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

你可能感兴趣的文章
FindChildControl与FindComponent
查看>>
中国城市json
查看>>
android下载手动下载Android SDK
查看>>
C++学习:任意合法状态下汉诺塔的移动(原创)
查看>>
leetcode133 - Clone Graph - medium
查看>>
UNET学习笔记2 - 高级API(HLAPI)
查看>>
"ORA-00942: 表或视图不存在 "的原因和解决方法[转]
查看>>
Oauth支持的5类 grant_type 及说明
查看>>
C#中用DateTime的ParseExact方法解析日期时间(excel中使用系统默认的日期格式)
查看>>
W3100SM-S 短信猫代码发送 上
查看>>
netty接收大文件的方法
查看>>
软件工程设计之四则运算
查看>>
SpringMVC @ResponseBody 406
查看>>
Partial Tree UVALive - 7190(完全背包)
查看>>
Happy Number
查看>>
vue中ESlint报错
查看>>
0816 1459 json & pickle ,目录导入,目录规范
查看>>
HDU 1398
查看>>
如何恢复oracle中已删除的表
查看>>
GridView中的CheckBox选中 (JQuery)
查看>>