前言:
随着C语言和数据结构初阶完结,终于进入了C++的学习阶段,本篇作为【C++】系列的第一篇,主要总结一些入门基础知识,帮助大家快速上手写出简单的C++代码。
- 作者:如何写出最优雅的代码
- 如有错误,敬请指正
面向对象程序设计(Object Oriented Programming,OOP),是一种计算机编程架构。OOP的一条基本原则是计算机程序由单个能够起到子程序作用的单元或对象组合而成,OOP达到了软件工程的三个主要目标:重用性、灵活性和扩展性。OOP = 对象 + 类 + 继承 + 多态 + 消息,核心概念是类和对象
面向对象程序设计方法是尽可能模拟人类的思维方式,使软件的开发方法与过程尽可能接近人类认识世界、解决现实问题的方法和过程,也即使得描述问题的问题空间和解决方案空间在结构上尽可能一致,把客观世界中的实体抽象为问题域中的对象
面向对象程序设计以对象为核心,该方法认为程序由一系列对象组成。**类是对现实世界的抽象,包括表示静态属性的数据和对数据的操作,对象是类的实例化。**对象通过消息传递互相通信,来模拟现实世界中不同实体间的联系。在面向对象的程序设计中,对象是组成程序的基本模块
面向对象程序设计主要有三大特性,即封装、继承和多态。
封装是指将一个计算机系统中的数据以及这个数据相关的一切操作语言(即描述每一个对象的属性以及其行为的程序代码)组装到一起,一并封装在一个有机的实体中,把他们封装在一个模块中,也就是一个类中,为软件结构的相关部件所具有的的模块性提供良好的基础。
在面向对象技术的相关原理以及程序语言中,封装的最基本单位是对象,而使得软件结构的相关部件的实现“高内聚、低耦合”的“最佳状态”便是面向对象技术的封装性所需要实现的最基本的目标。
对于用户来说,对象是如何对各种行为进行操作、运行、实现等细节是不需要刨根问底了解清楚的,用户只需要通过封装外的通道对计算机进行相关方面的操作即可。大大地简化了操作的步骤,使用户使用起计算机来更加高效、更加得心应手。
继承性是面向对象技术中的另外一个重要特点,其主要指的是两种或者两种以上的类之间的联系与区别。继承,顾名思义,是后者延续前者的某些方面的特点,而在面向对象技术则是指一个对象针对于另一个对象的某些独有的特点、能力进行复制或者延续。
如果按照继承源进行划分,则可以分为单继承(一个对象仅仅从另外一个对象中继承其相应的特点)与多继承(一个对象可以同时从另外两个或者两个以上的对象中继承所需要的特点与能力,并且不会发生冲突等现象);如果从继承中包含的内容进行划分,则继承可以分为四类,分别为取代继承(一个对象在继承另一个对象的能力与特点之后将父对象进行取代)、包含继承(一个对象在将另一个对象的能力与特点进行完全的继承之后,又继承了其他对象所包含的相应内容,结果导致这个对象所具有的能力与特点大于等于父对象,实现了对于父对象的包含)、受限继承、特化继承。
从宏观的角度来讲,多态性是指在面向对象技术中,当不同的多个对象同时接收到同一个完全相同的消息之后,所表现出来的动作是各不相同的,具有多种形态;从微观的角度来讲,多态性是指在一组对象的一个类中,面向对象技术可以使用相同的调用方式来对相同的函数名进行调用,即便这若干个具有相同函数名的函数所表示的函数是不同的。
面向对象程序设计中的概念主要包括:对象、类、数据抽象、继承、动态绑定、数据封装、多态性、消息传递。
面向对象出现以前,结构化程序设计是程序设计的主流,结构化程序设计又称面向过程的程序设计。在面向对象程序设计中,问题被看做一系列需要完成的任务,函数(这里泛指例程、函数、过程)用于完成这些任务,解决问题的焦点集中于函数。其中函数是面向过程的,即它关注如何根据规定的条件来完成指定的任务。
比较面向对象程序设计和面向过程程序设计,面向对象程序设计还有以下优点:
C++是在C的基础上,容纳进去了面向对象编程思想,并增加了许多有用的库,以及编程范式等,熟悉C语言之后,对学习C++有一定的帮助,C++快速入门主要是补充C语言语法的不足,以及C++是如何对C语言设计不合理的地方进行优化的,为后续学习类与对象打下基础
C++总计63个关键字,C语言32个关键字,这里提供关键字表,一些关键字的具体用法在之后使用到的地方在做详细说明。
asm | do | if | return | try | continue |
---|---|---|---|---|---|
auto | double | inline | short | typedef | for |
bool | dynamic_cast | int | signed | typeid | public |
break | else | long | sizeof | typename | throw |
case | enum | mutable | static | union | wchar_t |
catch | explicit | namespace | static_cast | unsigned | default |
char | export | new | struct | using | friend |
class | extern | operator | switch | virtual | register |
const | false | private | template | void | true |
const_cast | float | protected | this | volatile | while |
delete | goto | reinterpret_cast |
在C/C++中,变量、函数和类都是大量存在的,这些变量、函数和类的名称若都存在于全局域中可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
定义命名空间,需要使用namespace关键字,后面跟命名空间的名字,然后接一对 { } 即可,{ } 中即为命名空间的成员。下面看一段简单的代码:
//Test,h
//同一个工程中允许存在多个相同名称的命名空间,编译器最后会合并成同一个命名空间
//Test.h中的命名空间hanhan会和Test.cpp中的命名空间hanhan合并
#pragma once
#include
using namespace std;
namespace hanhan
{
int Sub(int x, int y)
{
return x - y;
}
void Print()
{
int i = 3;
while (i--)
{
cout << "憨憨" << endl;
}
}
}
//Test.cpp
//hanhan是命名空间的名字,一般开发中是用项目名字做命名空间的名字
//这里我使用了人名 憨憨 作为命名空间名字演示
//在命名空间中可以定义变量、函数、类型,命名空间也可以嵌套定义
#include "Test.h"
namespace hanhan
{
struct Student
{
char name[10];
int stu_id;
int age;
};
int a = 3;
int b = 7;
int Add(int x, int y)
{
return x + y;
}
namespace wuyu
{
int a = 3;
int m = 77;
}
}
//使用using将m引入,后面可以直接访问
using hanhan::wuyu::m;
int main()
{
//struct Student stu = { "憨憨",2020310361,18 };//这样使用,error,找不到Student类型
hanhan::Student stu = { "憨憨",2020310361,18 };//在未使用using声明使用憨憨命名空间时,可以通过操作符::去访问命名空间内的成员
cout << stu.name << " " << stu.stu_id << " " << stu.age << endl;
hanhan::a = 7;
cout << hanhan::a << endl;//7
cout << hanhan::wuyu::a << endl;//3
cout << hanhan::Add(3, 3) << endl;//6
cout << m << endl;//77
hanhan::Print();
return 0;
}
命名空间的使用,可以通过::操作符,命名空间名::要访问的成员,来访问某个成员。也可以通过使用using将命名空间中某个成员引入,如上面代码中 using hanhan::wuyu::m 就是引入了m,使得在后面的代码中可以直接访问。还可以使用using namespace 命名空间名称 将整个命名空间的成员都引入,比如上面代码中的using namespace std 就是将std中的所有成员引入,使得cout可以直接访问操作
咱们先看一段代码,后面再进行分析。
//C++的问候
#include
using namespace std;
int main()
{
char name[10] = { 0 };
int age = 0;
cout << "请输入姓名和年龄" << endl;
//输入
cin >> name >> age;// 憨憨 20
//cin >> name;
//cin >> age;
//输出
cout << "你好," << age << "岁的" << name << ",欢迎来到C++的世界" << endl;
//你好,20岁的憨憨,欢迎来到C++的世界
return 0;
}
分析:
还需注意的一点是:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需要包含对应的头文件即可。后来将其实现在std命名空间下,为了和C语言的头文件区分,也为了正确使用命名空间,规定C++不带.h,旧版编译器(vc 6.0)中还支持
std是C++标准库的命名空间,如何展开std使用比较合理呢?
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定实参。
#include
using namespace std;
void Test(int a = 33)
{
cout << a << endl;
}
int main()
{
Test();//未传参,33
Test(77);//传参,77
return 0;
}
缺省参数有两种:全缺省参数、半缺省参数
即该函数的所有形参均指定缺省值。
#include
using namespace std;
void Test(int x = 3, int y = 4, int z = 5)
{
cout << "x = " << x << endl;
cout << "y = " << y << endl;
cout << "z = " << z << endl;
cout << "x + y + z = " << x + y + z << endl;
cout << "----------------" << endl;
}
int main()
{
Test(11, 22, 33);
Test(11, 22);
Test(11);
Test();
return 0;
}
在函数的参数列表中,从右往左连续给参数缺省值,只要不是每一个参数都有,则称为半缺省参数。
#include
using namespace std;
//缺省参数必须从参数列表的右边开始往左给缺省值
void Test(int x, int y = 4, int z = 5)
{
cout << "x = " << x << endl;
cout << "y = " << y << endl;
cout << "z = " << z << endl;
cout << "x + y + z = " << x + y + z << endl;
cout << "----------------" << endl;
}
int main()
{
Test(11, 22, 33);
Test(11, 22);
Test(11);
//Test();//error,第一个参数未给缺省值
return 0;
}
在自然语言中,重载,即一个词有多重含义,人们可以通过上下文来判断该词的真实含义,即该词被重载了。
函数重载是函数的一种特殊情况,C++允许在同一个作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或参数类型或类型顺序中至少有一个)不同,常用来处理实现功能类似数据类型不同的问题。
#include
using namespace std;
int Add(int a, int b)
{
return a + b;
}
double Add(int a, double b)
{
return a + b;
}
double Add(double a, double b)
{
return a + b;
}
int Add(int a, int b, int c)
{
return a + b + c;
}
int main()
{
cout << Add(3, 3) << endl;
cout << Add(2, 7.2 ) << endl;
cout << Add(9.9, 0.1) << endl;
cout << Add(3, 3, 3) << endl;
return 0;
}
其实从这里我们也可以联想到cout和cin使用时为什么不用指定类型,因为他们运用了函数重载,定义了功能类似但数据类型不同的实现。
C语言没办法支持重载,因为同名函数没办法区分,而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,所以就支持了重载。这里简单提一下,不需要太深入的了解。
此外,如果两个函数的函数名称和形参列表一样,但返回类型不一样,也不能构成重载,因为调用时编译器无法区分。
引用不是新定义一个变量,而是给已存在的变量取了一个别名,编译器不会再为引用变量开辟内存空间,它和它的引用的变量共同用一块内存空间。
定义:类型& 引用变量名(对象名) = 引用实体
引用类型必须和引用实体是同种类型
#include
using namespace std;
int main()
{
int n = 3;
int& s = n;
cout << &n << endl;
cout << &s << endl;
cout << "n=" << n << " " << "s=" << s << endl;
s = 7;
cout << "n=" << n << " " << "s=" << s << endl;
return 0;
}
#include
using namespace std;
int main()
{
const int a = 10;
//int& a ra = a;//error,a为常量
const int& ra = a;
//int& b = 10;//error,b为常量
const int& b = 10;
double d = 3.14;
//int& rd = d;//error,类型不同
//double& rd = d;
const int& rd = d;
return 0;
}
以值作为参数或者返回类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时拷贝,因此用值作为参数或返回类型时,效率是非常低的,尤其是当参数或者返回类型非常大时,效率就更低。
在语法概念上,引用就是一个别名,没有独立空间,和引用实体共用同一块空间。但其实在底层的实现上,引用实际是空间的,因为引用是按照指针方式来实现的。
#include
using namespace std;
int main()
{
int n = 3;
int& rn = n;
rn = 7;
int* pn = &n;
*pn = 7;
return 0;
}
调试转到反汇编,可以看到底层的具体操作是一样的
不同点小结:
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数可以提升程序运行效率
C++引入内联其实就是为了替代宏函数。
宏的优点:
宏的缺点:
C++中可以使用const、enum替换常量定义,使用内联函数替换短小函数定义。
随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在类型难于拼写,含义不明确导致容易出错。当类型名字很长,有时我们也会考虑使用typedef给类型取别名,虽然简化的代码,但也给我们带来新的问题,要求我们在声明变量时要清楚的知道表达式的类型,显然一两个typedef还可以接受,但程序很复杂的时候就不是很好用了,因此C++11给auto赋予了新的含义。
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它。
C++11中,标准委员会赋予了auto全新的含义:auto不再是一个存储类型指示符,而是作为一个新的变量类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
#include
using namespace std;
int Func()
{
return 3;
}
int main()
{
int a = 3;
auto b = a;
auto c = 'a';
auto d = Func;
auto e = Func();
//使用typeid获取变量的类型
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
cout << typeid(e).name() << endl;
return 0;
}
在C++98中遍历数组的方式:
#include
using namespace std;
void Test()
{
int arr[] = { 4,5,6,1,2,3,7,8,9,0 };
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
arr[i]--;
}
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
cout << arr[i] << " ";
}
}
int main()
{
Test();
return 0;
}
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会任意出错。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号 “:” 分成两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
#include
using namespace std;
void Test()
{
int arr[] = { 4,5,6,1,2,3,7,8,9,0 };
for (auto& a : arr)
{
a--;
}
for (auto a : arr)
{
cout << a << " ";
}
}
int main()
{
Test();
return 0;
}
与普通循环类似,在循环内部可以进行continue和break。
void Test(int arr[])
{
for (auto& a : arr)//error
{
a--;
}
}
C++98中的指针空值NULL,实际上是一个宏,在传统的C头文件stddef.h中,有如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到,NULL可能被定义字面常量0,或者被定义为无类型指针(void*)的常量,不论采取何种定义,在使用空值指针时,都不可避免会出现一些问题。
在C++98中,字面常量0既可以是一个整型数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个1整型常量,如果要将其按照指针方式来使用,必须对其进行强制转换 (void*)0 。
注意:
C++入门的基础知识就先总结到这了,看到这如果觉得有所收获,可以关注一下哦,跟着我一起学C++,后面会逐渐更新C++学习阶段的博客。
@如何写出最优雅的代码