C++语言的发展简史
- 将程序设计语言分为低级语言、中级语言和高级语言。****机器语言和汇编语言属于低级语言一类,因为它们能够直接操纵计算机的寄存器和内存。机器语言是一种依赖于CPU的指令系统,使用机器指令的二进制代码编写程序,能够直接被计算机识别。汇编语言使用能够代表指令的助记符来编写程序,可以看作是符号化了的机器语言。
- 高级语言是面向用户的语言,很多语言在形式上接近于算术语言和自然语言,程序员编写方便。使用高级语言编写的程序易读且通用性强,但大部分不能直接与硬件打交道,也不能直接在计算机上运行,需要系统软件的支持,如需要编译程序和链接程序将高级语言编译链接为机器指令后才能运行。
- C语言是C++语言的前身,在进一步扩充和完善C语言的基础上得到了C++语言。
C++语言的特点
- 它是C语言的继承,尽量兼容C语言,既保持了C语言的简洁和高效,可以像C语言那样进行结构化程序设计,同时也增强了C语言对类型的处理。
- 加入了面向对象的特征,可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。
与C语言相比,C++语言的优点:
- 从程序运行的稳定性来说,C++语言比C语言更安全,它支持过程化编程、面向对象编程和泛型编程。因为能够支持面向对象的开发方式,所以C++语言的应用领域更加广泛。
- C++语言可以运行于多种平台上,如Windows、MAC操作系统及UNIX等多种版本。
- C++语言中加入了面向对象的概念,虽然C语言的语法绝大多数被保留在C++语言中,但C++的程序结构和C语言的程序机构存在很大差别。
- C++语言对C语言做了很多改进,C++语言相对于C语言最根本的变化是引进了类和对象的概念。
基本的输入/输出
功能 | C语言中使用函数 | C++语言中提供类 | C++类中对象 | 运算符 |
---|---|---|---|---|
键盘输入 | scanf() |
输入流类istream
|
cin |
>> |
屏幕输出 | printf() |
输出流类ostream
|
cout |
<< |
头文件和命名空间
-
iostream
是C++的标准输入/输出流。当在程序中使用cin
或cout
时,必须在程序的最前面包含这个流。如果还要使用其他的内容,那么需要包含其他的头文件。每条#include
指令只可以包含一个头文件,如果需要包含多个头文件,则需要使用多条#include
嵌入指令。 - 在C++中,头文件不再以
.h
结尾,以.h
结尾的头文件是C语言中常用的头文件。
常用的头文件有以下这些:
- 标准输入/输出流:
<iostream>
- 标准文件流:
<fstream>
- 标准字符串处理函数:
<string>
- 标准数学函数:
<cmath>
- 当使用
<尖括号>
时,C++编译器将首先在C++系统设定的目录中寻找要包含的文件,如果没有找到,再到指令中指定的目录中去查找。 - 当使用
"双引号"
时,C++编译器在用户当前目录下或指令中指定的目录下寻找要包含的文件。
C++为了避免命名冲突,特别引入了“命名空间”的定义,即namespace
。命名空间的作用是为了消除同名引起的歧义。
using namespace std;//使用命名空间
//定义如下
namespace 命名空间名
{
命名空间内的各种声明。。。。
(函数声明、类声明。。。)
}
强制类型转换运算符
static_cast
用于将一种数据类型转换成另一种数据类型,使用格式如下:
//格式
static_cast<type>(expression)
void tranform() {
double d = 3.1415;
//下面四种写法都是正确的
int a0 = static_cast<int>(d);//强制类型转换
int a1 = int(d);//强制类型转换运算符的新形式
int a2 = (int)d;//强制类型转换运算符的旧形式
int a3 = d;//自动类型转换
std::cout << "a0 = " << a0 << std::endl;//a0 = 3
std::cout << "a1 = " << a1 << std::endl;//a1 = 3
std::cout << "a2 = " << a2 << std::endl;//a2 = 3
std::cout << "a3 = " << a3 << std::endl;//a3 = 3
std::cout << "d = " << d << std::endl;//d = 3.1415
}
函数参数的默认值
C++语言规定,提供默认值时必须按从左至右的顺序提供,即有默认值的形参必须在形参列表的最后。如果有某个形参没有默认值,则它左侧的所有形参都不能有默认值。
调用函数时,主调函数的实参与被调函数的形参按从左至右的顺序进行匹配对应。
引用和函数参数的传递
1.引用的定义
引用相当于给变量起了个别名,变量对应于某个内存地址,如果给某个变量起了别名(不需要给它另开辟内存单元),相当于变量和这个引用都对应到同一个地址。
程序中使用哪个名字都是允许的。在C++中引用的定义格式如下:
void refrensh() {
int a = 10;//在栈区分配一个变量a,并把常量区的10的地址赋值给变量a
int &b = a;//在栈区分配一个变量b,访问变量b中所保存的地址,把a中保存的地址赋值给b
std::cout << &a << std::endl;//0x7ffeefbff52c
std::cout << &b << std::endl;//0x7ffeefbff52c
}
2.引用在函数中的使用
- 在程序中不仅能定义变量的引用,还可以将引用用在函数中。引用既可以作为函数的参数使用,也可以作为函数的返回值使用。
- 在C++中,函数调用时参数的传递有两种:传值和传引用。
- 传值,实际上是传递的对象的值。
- 传引用,是传递对象的首地址。
- 如果函数的形参不是引用,那么调用时,实参传递给形参通常的方式是传值的方式,即将实参的值拷贝给形参。在函数执行过程中,都是对这个拷贝做操作的,函数执行完毕返回后,形参的值并不拷贝回实参,也就是说函数内部对形参的修改不会影响到函数外部的实参的值。
- 如果函数的形参是引用,则调用时实参传递给形参的是传引用的方式。函数调用时,实参对象名传递给形参对象名,形参对象名就成了实参对象名的别名,即形参是对应实参的引用,它们是等价的,代表同一个对象,也可以看做是将实参的地址传递给了形参。在函数内部对形参的操作,都是对这个地址的内容进行的,相当于对实参的值做了操作。所以,当函数执行完毕返回后,实参的变化被保留下来。
#include <iostream>
void swapeValue(int a, int b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
std::cout << "swapeValue函数内:a = " << a << ", b = " << b << std::endl;
}
void swapeRef(int &a, int &b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
std::cout << "swapeRef函数内:a = " << a << ", b = " << b << std::endl;
}
int &refFunc(int &x) {
return x;
}
int main(int argc, const char * argv[]) {
int a = 10, b = 15;
swapeValue(a, b);
//swapeValue函数内:a = 15, b = 10
std::cout << "swapeValue函数外:a = " << a << ", b = " << b << std::endl;
//swapeValue函数外:a = 10, b = 15
swapeRef(a, b);
//swapeRef函数内:a = 15, b = 10
std::cout << "swapeRef函数外:a = " << a << ", b = " << b << std::endl;
//swapeRef函数外:a = 15, b = 10
int x = 30;
refFunc(x) = 35;//返回值是引用的函数调用表达式可以作为左值使用
std::cout << "返回值为引用:x = " << x << std::endl;
//返回值为引用:x = 35
return 0;
}
const与指针共同使用
const
修饰指针变量时,其基本含义如下:
-
如果唯一的
const
位于符号*
的左侧,表示指针所指数据是常量,数据不能通过本指针改变,但可以通过其他方式进行修改;指针本身是变量,可以指向其他内存单元。void constFunc1() { int temp = 10; int temp1 = 20; //`const`位于`*`左侧,修饰`int *`类型, //表示指针所指数据为常量 *a = &temp1;❌ //不能通过该指针修改数据 const int *a = &temp; //指针本身是变量,可以指向其他地址 a = &temp1; std::cout << "a = " << *a << std::endl;//a = 20 //也可以通过其他方式修改 temp1 = 11; std::cout << "a = " << *a << std::endl;//a = 11 }
-
如果唯一的
const
位于符号*
的右侧,表示指针本身是常量,不能让该指针指向其他内存地址;指针所指向的数据可以通过本指针就行修改。void constFunc2() { int temp = 10; int temp1 = 20; //`const`位于`*`右侧,修饰指针变量, //表示指针本身为常量 int * const a = &temp; //该指针不能指向其他内存地址 //a = &temp1; ❌ //指针所指向的数据可以通过本指针就行修改 *a = temp1; std::cout << "a = " << *a << std::endl;//a = 20 std::cout << "temp = " << temp << std::endl;//temp = 20 }
-
在符号
*
左右各有一个const
的时候,表示指针和指针所指的数据都是常量,既不能让指针指向其他地址,也不能通过指针修改所指向的内容。void constFunc3() { int temp = 10; int temp1 = 20; //指针和指针所指的数据都是常量 const int * const a = &temp; //既不能让指针指向其他地址 // a = &temp1; ❌ //也不能通过指针修改所指向的内容 // *a = &temp1; ❌ }
简单助记规则
const
修饰其左侧内容
如果const
是本行第一个标识符,则修饰右侧内容
内联函数
- 为了避免这种频繁的函数调用和返回,c++语言引入了内联函数的概念。使用内联函数,编译器在编译时并不生成函数调用,而是将程序中出现的每一个内联函数的调用表达式直接用该内联函数的函数体进行替换,就像整个函数体在被调用处被重写了一遍一样。很显然,使用内联函数会使最终可执行程序的体积增大,这是以空间消耗节省时间开销。
- 内联函数应该定义在前,调用在后,定义时只需在函数头返回类型前面加上关键字
inline
。 - 内联函数主要用于代码量少的函数,频繁调用。
- 如果函数体中有循环语句或
switch
语句,则通常不定义为内联函数。
函数的重载
所谓函数重载,是指在程序的同一范围内声明几个功能类似的同名函数。
实现函数重载必须满足下列条件之一:
- 参数表中对应的参数类型不同
- 参数表中参数个数不同
如果函数参数表中不同类型参数的次序不同,也符合上面所说的条件。要注意的是,返回值类型不能用来区分函数,也就是说,如果两个函数的名字和参数表都是一样的,仅仅返回值类型不同,则这两个函数不是重载的,编译器认为它们是重复定义,会编译报错。
编译阶段,程序还没有执行,所以并不知道返回值是什么,更加确定不了它的类型,所以编译器不能根据返回值来判断该调用哪个函数。
int maxFunc(int a, int b) {
return a > b ? a : b;
}
float maxFunc(float a, float b) {
return a > b ? a : b;
}
double maxFunc(double a, double b) {
return a > b ? a : b;
}
采用引用参数,也不能区分函数
//错误的重载函数❌
void log(double);
void log(double&)
二义性
//产生二义性❌
int sun(int a, int b, int c = 0) {
return a + b + c;
}
int sun(int a, int b) {
return a + b;
}
指针和动态内存分配
使用new
运算符动态申请的内存空间,需要在使用完毕时释放。C++提供了delete
运算符,用来释放动态分配的内存空间。delete
运算符基本用法如下:
delete 指针;//释放动态分配的内存空间
delete [] 数组指针;//释放数组
delete
运算符后面的指针必须是指向动态分配的内存空间的,否则运行时和可能出错。
用string
对象处理字符串
c++
标准模板库中提供了string
数据类型,专门用于处理字符串。string
是个类,这个类型的变量称为string对象
。
1.string
对象的声明
要在程序中使用string
对象,必须在程序中包含头文件string
,即在程序的最前面加上如下语句:
#include <string>
声明一个string
对象,和声明普通变量是类似的,格式如下:
string 变量名;
void declareString() {
//声明string对象str,值为空
std::string str;
//声明string对象city,并使用字符串常量进行初始化
std::string city = "YangCheng";
//声明string对象str2,并使用字符串变量进行初始化
std::string str2 = city;
//使用字符串数组对string对象初始化
char name[] = "C++程序";
std::string pName = name;
//声明一个string对象数组
std::string strArr[] = {
"beijing",
"Shanghai",
"shenzhen",
"guangzhou"
};
}
2.string
对象的操作
字符串赋值、取值、比较、拼接
void stringOpt1() {
std::string s1, s2;
s1 = "C++程序";
s2 = s1;
std::string s3;
std::cout << "s3 = " << s3 << std::endl;
//输出:s3 =
s3 = s1 + s2;
std::cout << s1 + s2 << std::endl;
//输出:C++程序C++程序
std::cout << "s3 = " << s3 << std::endl;
//输出:s3 = C++程序C++程序
s3 += "de";
std::cout << "s3 = " << s3 << std::endl;
//输出:s3 = C++程序C++程序de
bool b = s1 < s3;
std::cout << "bool = " << b << std::endl;
//输出:bool = 1
char c = s1[2];
std::cout << "c = " << c << std::endl;
//输出:c = +
std::cout << "s1[2] = " << s1[2] << std::endl;
//输出:s1[2] = +
char arrStr[] = "hello";
s3 = s1 + arrStr;
std::cout << "s3 = " << s3 << std::endl;
//输出:s3 = C++程序hello
}
字符串获取长度、判空、拼接、获取容量、截取、插入
void stringOpt2() {
std::string str;
if (str.empty()) {
std::cout << "str is null, " << "length = " << str.length() << std::endl;
//输出:str is null, length = 0
} else {
std::cout << "str is not null" << std::endl;
}
str = str.append("abcdefg");
std::cout << "str = " << str << ", size = " << str.size() << std::endl;
//输出:str = abcdefg, size = 7
const char *p = str.c_str();
std::cout << "p = " << p << std::endl;
//输出:p = abcdefg
//查找字符串“de”,从第n个下标开始
std::cout << "find: " << str.find("de", 0) << std::endl;
//输出:find: 3
std::cout << "find: " << str.find("de", 4) << std::endl;
//输出:find: 18446744073709551615
//查找失败
//从第n个下标开始插入字符串“123”
std::string str1 = str.insert(4, "123");
std::cout << "str1 = " << str1 << std::endl;
//输出:str1 = abcd123efg
}
C++语言的程序结构
C++程序以.cpp
作为文件扩展名,文件中包含若干个类和若干个函数。程序中必须有且仅有一个主函数main(),这是程序执行的总入口。主函数也称为主程序。程序从主函数main()的开始处执行,主函数可以在任何地方出现,按照其控制结构,一直执行到结束。
程序的结束通常是遇到了以下两种情形之一:
- 在主函数中遇到
return
语句。 - 执行到主函数最后面的括号。
主函数中可以调用程序中定义的其他函数,但其他函数不能调用主函数。主函数仅是系统为执行程序时所调用的。
C++程序中,仍沿用C语言的注释风格,即注释有以下两种形式:
- 从
/*开始,到*/结束
,这之间的所有内容都视作注释。 - 从
//直到行尾
,都是注释。