目录
[3. 现代发展](#3. 现代发展)
一、面向对象编程
面向对象编程(Object-Oriented Programming,简称OOP)是一种计算机编程范式,它使用"对象"来设计应用程序和计算机程序。这种编程范式强调数据和操作数据的函数(方法)的封装,以及对象之间的交互。面向对象编程的由来和发展可以追溯到20世纪60年代,以下是其主要的发展历程:
1.早期概念
-
Simula语言(1960年代):被认为是第一个引入面向对象概念的编程语言。Simula引入了类(class)和对象(object)的概念,以及继承和动态绑定等特性。
-
Smalltalk语言(1970年代):由Alan Kay等人开发,是第一个完全基于面向对象概念的编程语言。Smalltalk对后来的面向对象编程语言产生了深远影响,它定义了许多现代OOP语言的特性,如消息传递、类和对象、继承等。
2.发展与普及
-
C++语言(1980年代):由Bjarne Stroustrup开发,最初称为"C with Classes"。C++在C语言的基础上增加了类和对象的概念,支持面向对象编程,同时也保持了对C语言的兼容性。C++的成功使得面向对象编程开始广泛流行。
-
Objective-C语言(1980年代):由Brad Cox和Tom Love开发,是另一种支持面向对象编程的语言,后来被苹果公司用于开发Mac OS X和iOS应用程序。
-
Java语言(1990年代):由Sun Microsystems的James Gosling等人开发。Java是一种完全面向对象的编程语言,它的跨平台特性(通过Java虚拟机实现)和简单性使其迅速成为主流编程语言之一。
-
Python、Ruby等语言(1990年代至今):这些语言都支持面向对象编程,并且各自以简洁的语法和强大的功能吸引了大量开发者。
3. 现代发展
-
多范式编程语言:现代编程语言如C#、JavaScript、Swift等,虽然支持面向对象编程,但也支持其他编程范式,如函数式编程。
-
设计模式和框架:随着面向对象编程的普及,出现了许多设计模式(如单例模式、观察者模式等)和框架(如Spring、Hibernate等),这些都极大地丰富了面向对象编程的实践。
面向对象编程的核心概念包括封装、继承和多态,这些概念使得代码更加模块化、可重用和易于维护。随着技术的发展,面向对象编程继续在软件开发领域扮演着重要角色。
二、从C语言到C++
C++是C的扩展,C是C++的子集,C++包括C的全部特征、属性和优点。同时,增加了对面向对象编程的完全支持。接下来我们先了解C++一些特点,帮助大家更好的从C过度到C++的基本使用。
1.关于堆内存的使用
C++中申请堆空间用的是new与delete运算符,这两个类似于c语言中的malloc与free函数。
new使用方式如下所示:
语法:
指针变量 = new 数据类型:
指针变量 = new 数据类型[长度n];
例如:
int *p; pnew int;
char *pStr = new char[50];
delete使用方式如下:
语法:
delete 指针变量:
delete[]指针变量;
例如:
delete p;
delete [] pStr;//对于数组
代码示例:
cpp
#include <iostream>
using namespace std;
int main() {
int* p = new int(33);//分配一个整数空间4字节,圆括号代表初始化值
cout << *p << endl;
int* p2 = new int[10];//分配连续的10个整数空间40字节
delete p;
delete[] p2;
return 0;
}
我们可以把new和delete对比着malloc与free来学习:
1)从示例中可以看出使用new申请空间并不是以字节为单位的,而是以数据类型,从而
也无需强制转换。
2)要申请多个数据,需要使用了中括号,释放这块内存的时候也需要中括号。
3)注意中括号和圆括号的差别,圆括号是初始化,中括号是申请多个的意思。
2.关于函数重载
相同的作用域,如果两个函数名称相同,面参数不同,我们把它们称为函数重载,函数
重载又称为函数的多态性。请一定注意那个前提:相同的作用域、相同的作用域,相同的作用域。
以下几种方式会构成函数重载
形参数量不同
形参类型不同
形参的顺序不同
形参数量和形参类型都不同
总而言之,就是参数的不同会构成重载,那么构成了重载有什么用处呢?
如果我们的算法需要支持多个数据类型,比如求两个数的和,想要支持整数,浮点数甚至字符串。
如果没有函数重载实现如下:
cpp
int AdditionInt(int numA, int numB) {
return numA + numB;
}
double AdditionDouble(double numA, int numB) {
return numA + numB;
}
int AdditionStr(char* pNumA, char* pNumB) {
int nNumA = 0;
int nNumB = 0;
sscanf_s(pNumA, "%d", &nNumA);
sscanf_s(pNumB, "%d", &nNumB);
return nNumA + nNumB;
}
对于函数的使用者而言,需要记住三个功能类似,但是名称不同的函数。而代码写成下面的样子,就可以解决这个问题:
cpp
int Addition(int numA, int numB) {
return numA + numB;
}
double Addition(double numA, int numB) {
return numA + numB;
}
int Addition(char* pNumA, char* pNumB) {
int nNumA = 0;
int nNumB = 0;
sscanf_s(pNumA, "%d", &nNumA);
sscanf_s(pNumB, "%d", &nNumB);
return nNumA + nNumB;
}
此代码在C语言中会因为函数重名而编译失败,但在C++中这则是一个典型的重载函数C++会根据传入函数参数的不同进而决定调用哪个函数。有了这个机制,使用者仅仅需要记住一个函数名即可,随意的往里面传递各种数据类型。函数重载的本质,以上的同名函数,在C++看来实际上是不同的函数名称,C++为了支持这种特性,进而提出了名称粉碎/名字改编(name managling)机制,以上面的函数为例,他们的函数名分别不同。根据函数参数的不同,把函数修改成了不同的名字。
3.关于默认参数
在C++中,还有一个不错的机制,叫做默认参数,函数声明或者定义的时候,可以给形参赋一些默认值,调用函数时,若没有给出实参,则按指定的默认值进行工作。
cpp
int Addition(int a, int b, int c = 0, int d = 0, int e = 0) {
return a + b + c + d + e;
}
在以上这个例子中,调用函数的时候,可以传递两个参数,也可以传递三个,四个,五传递的参数不足五个的时候,后面的会默认的被传递0。个。需要注意的几个问题:
1)函数没有声明时,在函数定义中指定形参的默认值
2)函数既有定义又有声明时,声明时指定后,定义后就不能再指定默认值
3)当使用了默认参数的同时还使用了重载容易造成二义性
cpp
#include<iostream>
using namespace std;
int fun(int a, int b = 5) {
//声明与定义在一起,直接写默认参数就可以
return a + b;
}
int fun(int a, int b, int c = 3);//1声明与定义不能同时带默认参数int //2这里造成了二义性
int main(void) {
cout << fun(3) << endl;
cout << fun(3, 4) << endl;
cout << fun(3, 4, 5) << endl;
return 0;
}
int fun(int a, int b, int c) {
return a + b + c;
}
4)默认值的定义必须遵守从右到左的顺序,如果某个形参没有默认值,则它左边的参数就不能有默认值。
cpp
void funcl(int a, double b = 4.5, int c = 3);//合法
void funcl(int a = 1, double b, int c = 3);//不合法
函数调用时,实参与形参按从左到右的顺序进行匹配。
4.引用
引用是C++引入的新语言特性,是C++常用的一个重要内容之一,正确、灵活地使用引用,可以使程序简洁、高效。
引用是给一个变量起别名,定义引用的一般格式:
类型 &引用名 = 变量名;
int nSize;
&nNum = nSize;
需要注意的是:
1)由于引用就是变量的一个别名,因此上例中nSize的地址与m的地址是完全一样的;
2)定义引用时一定要初始化,指明该引用变量是谁的别名;
3 )引用一经初始化不能再引用其他对象。
示例代码:
cpp
#include <iostream>
using namespace std;
int main(void) {
int val = 100;
//int &refval; //Error,引用必须初始化
int& refval = val;
refval = 200;//将200赋值给refval//实际上改变的是val这个变量
cout << "val=" << val << endl;
int val2 = 500;
refval = val2;//不代表将refval引用至va12这个变量//仅仅只是代表将va12赋值给refval
cout<< "val="<<val<<endl;
}
5.引用参数
引用传递方式是在函数定义时在形参前面加上引用运算符"&"
例如:int swap(int &nNumA, int &nNumB);
按值传递方式容易理解,但形参值的改变不能对实参产生影响,地址传递方式通过形参的改变使相应的实参改变,但是C++的设计者认为这恰巧是C语言程序容易产生错误且难以阅读的原因。
于是引用在这里发挥了重要的作用,引用作为参数对形参的任何操作都能改变相应的实参的数据,又使函数调用显得方便、自然。
代码示例:
cpp
#include <iostream>
using namespace std;
//引用作为参数传递
void swap(int& x, int& y) {
int temp = x;
x = y;
y = temp;
}
int main(void) {
int a = 5;
int b = 6;
swap(a, b);//在函数调用时,引用被初始化x=a,y=b;
cout<< "a=" << a <<"b=" << b << endl;
return 0;
}
引用和指针的区别:
引用访问一个变量是直接访问,而指针里面需要保存变量的地址,所以是间接访间
引用是一个变量的别名,本身不单独分配自己的内存空间,它不是一个单独的变量,而指针有自己的内存空间
引用一经初始化不能再引用其它变量,而指针可以(非const指针)
尽可能使用引用,不得已时使用指针。
三种传参方式比较:
值传递:实参要初始化形参要分配空间,将实参内容拷贝到形参
指针传递:传递的是地址,能够间接修改函数外部的变量,其本质仍是值传递。
引用传递:实参初始化形参的时候不分配空间,而是形参实参融为一体,修改了形参就是改变了实参。
6.作用域符号
在C++中,双冒号::
被称为作用域解析运算符(Scope Resolution Operator)。它主要用于以下几个方面:
1. 访问全局变量 :当局部变量与全局变量同名时,可以使用::
来访问全局变量。例如:
cpp
int x = 10; // 全局变量
void someFunction() {
int x = 20; // 局部变量
std::cout << x << std::endl; // 输出局部变量x的值,即20
std::cout << ::x << std::endl; // 输出全局变量x的值,即10
}
2. 访问类成员 :在类的外部定义成员函数时,需要使用::
来指定该函数属于哪个类。例如:
cpp
class MyClass {
public:
void myFunction();
};
void MyClass::myFunction() {
// 函数实现
}
3. 访问命名空间成员 :当一个命名空间中的名称与另一个命名空间或全局作用域中的名称冲突时,可以使用::
来指定访问哪个命名空间中的成员。例如:
cpp
namespace MyNamespace {
int x = 10;
}
int x = 20;
int main() {
std::cout << x << std::endl; // 输出全局变量x的值,即20
std::cout << MyNamespace::x << std::endl; // 输出MyNamespace中的x的值,即10
return 0;
}
作用域解析运算符::
在C++中是一个非常关键的工具,它帮助程序员清晰地管理和访问不同作用域中的变量和函数,有效地解决了命名冲突的问题。
三、C++的输入输出机制
I/0流是指输入输出的一系列数据流,输出使用插入操作符"<<"向cout 输出流中插入字符。
在使用 cout 和 cin的时候,需要包含 iostream 这个头文件,并且使用 std 这个命名空间。如下所示:
cpp
#include <iostream>
using namespace std;
int main() {
cout << "this is a program";
return 0;
}
输入使用抽取操作符">>"从cin输入流中抽取字符
cpp
#include <iostream>
using namespace std;
int main() {
int myage;
cin >> myage;
int i; float f; long l;
cin >> i >> f >> l;
return 0;
}
C++的输出能够控制输出的数据格式,常用格式如下:
|------------------------------|-------------|
| 控制符 | 描述 |
| dec | 按10进制输出 |
| hex | 按16进制输出 |
| oct | 按8进制输出 |
| setfi1l(c) | 设填充字符为c |
| setprecision(n) | 设置显示小数精度为n位 |
| setw(n) | 设域宽为n个子符 |
| setiosflags(ios::fixed) | 小数方式表示 |
| setiosflags(ios::scientific) | 指数表示 |
| setiosflags(ios::left) | 左对齐 |
| setiosflags(ios::right) | 右对齐 |
输出宽度示例
cout<<setw(8)<<10<<endl;
若数值10和20均按照宽度8输出
cout<<setw(8)<<10<<setw(8)<<20<<endl;
如果一个值的位数大于setw(n)确定的宽度位数,则按原宽度输出,例如
float amount=4.53671;
cout<<setw(4)<<amount<<endl ;
输出八进制,十六进制和十进制数示例:
cpp
#include <iostream>
#include <iomanip>
#include<stdlib.h>
using std::wcout;
using std::endl;
using std::setiosflags;
using std::ios;
int main() {
int number = 1001;
wcout << L"Decimal:"<< std::dec << number << endl \
<< L"Hexadecimal :" << std::hex << number << endl \
<< L"Octal:" << L"Hexadecimal :" << std::hex \
<< std::oct << number << endl \
<< setiosflags(ios::uppercase) << number << endl;
system("pause");
return 0;
}
控制左右对齐输出,默认情况下,I/0流以右对齐格式显示。
cpp
#include <iostream>
#include <iomanip>#include<stdlib.h>
using std::endl; using std::setiosflags;
using std::setw; using std::wcout;
int main() {
wcout << setiosflags(std::ios::left) << setw(5) << 1 << setw(5) << 2 << setw(5) << 3 << endl;
wcout << setiosflags(std::ios::right) << setw(5) << 1 << setw(5) << 2 << setw(5) << 3 << endl;
system("pause");
return 0;
}
以上是 C++提供的输入输出方式,使用printf/scanf 依然也是可以的。
如下输出打印一个菱形图案:
cpp
#include <iostream>
#include <iomanip>
#include <stdlib.h>
using std::wcout;
using std::endl;
using std::setfill;
using std::setw;
int main() {
for (int i = 0; i < 7; i++) {
if (i < 4) {
wcout << setfill(L' ') << setw(4 - i) << L' ';
wcout << setfill(L'*') << setw(i * 2 + 1) << L'*' << endl;
}
else {
wcout << setfill(L' ') << setw(i - 2) << L' ';
wcout << setfill(L'*') << setw(i - (i - 4) * 3 + 1) << L'*' << endl;
}
}
system("pause");
return 0;
}