头文件引入语句和命名空间声明语句
#include <iostream> //用cont输入输出时需要用此头文件
using namespace std
#include <stdio.h> /*用printf输出时需要用此头文件*/
#include <string> //使用string类功能
输入输出函数cin,cout,printf
#include <iostream>
using namespace std;
//输入两个[数 x 和 y,求两数中的大者
int main() {
int a;
int b;
int c;
cin >> a >> b;
c = max(a, b);
cout << "the larger one is" << c;
printf("the larger one is %d\n",c); //类似功能
return 0;
}
int max(int x,int y) {
if (x>y)
{
return x;
}
if (y > x) {
return y;
}
else {
return y;
}
}
基本概念
类:类中可以包含两种成员:数据和函数
对象(object):具有“类”类型特征的变量称为“对象”,对象具有两个要素:属性(静态的) 和 行为(动态的)。
抽象(abstraction):抽象的作用是表示同一类事物的本质。类是抽象的——对象的抽象,对象是具体的——是类的实例
封装(encapsulation)把对象的内部实现和外界行为分隔开,对象的公有函数名就是对象的对外接口
消息:消息是对象之间发出的行为请求。在C++中,消息其实就是函数的调用。
继承(inheritance):是存在于两个类之间的一种父子关系。采用“继承”的方法可以很方便地利用已有的类建立一个新的类,可以重用已有软件中的一部分节省编程工作量。
多态(polymorphism):指事物的多种形态,表现为一名多用,能增加程序的灵活性(可扩展性)。在C++中,多态性有两种情况由继承而产生的相关的不同的类,其对象对同一消息会作出不同的响应,为动态多态。不同类对象或同类对象不同情况下,对同一消息会作出不同的响应,为静态多态。
面向对象程序设计的四大特点:抽象性,l封装性,继承性,多态性
面向过程&面向对象编程(非重点)
传统的面向过程程序设计是围绕功能进行的
- 用一个函数实现一个功能;
- 所有的数据都是公用的;
- 一个函数可以使用任何一组数据;
- 而一组数据又能被多个函数所使用。
面向对象程序设计的设计思路是面对一个个对象。程序设计者有两方面的任务:设计所需的各种类和对象; 考虑怎样向有关对象发送消息,以完成所需任务
面向对象的软件工程包括以下几个部分:
- 面向对象的分析( OOA ):分析用户的需求,建立面向对象模型。
- 面向对象的设计 ( OOD ):将模型细化,设计类。
- 面向对象的实现( OOP ): 选定一种面向对象的编程语言, 具体编码实现上一阶段类的设计。
- 面向对象的测试( OOT ): 发现程序中的错误并改正它。
- 面向对象的维护( OOSM ): 改进软件性能、修改程序。
字符串类
C++还提供了一种比使用字符数组更方便的方法,字符串类型
string string1 = "a";
string string2=“China”;
string1=string2; // 复制
string2[2]=‘a’; //替换
string3=string1 + string2;//连接
bool a = string2 > string1;//比较
常用string类的成员函数
- length()获得字符串长度
- replace()可对字符串进行替换
#include <string>
int main() {
string s = "hello world";
// 1. 修改字符串中某个字符(将第0个字符改为 'H')
s[0] = 'H';
// 2. 获取字符串长度
int len = s.length();
// 3. 替换字符串中从第6位开始的5个字符,替换为 "C++"
s.replace(6, 5, "C++");
// 输出结果
cout << "修改后的字符串: " << s << endl; //Hello C++
cout << "字符串长度: " << len << endl; //11
}
字符串变量中存放的是字符串的指针
//输入3个字符串,要求按字母由小到大顺序输出。
int main() {
string a, b, c,temp;
cout<<"请输入3个字符";
cin >> a >> b >> c;
// 如果第一个字符串比第二个大,就交换
if (a>b)
{
temp = a;
a = b;
b = temp;
}
//再次判断 str1 和 str3,确保 str1 是最小的。
if (a>c)
{
temp = a;
a = c;
c = temp;
}
// 最后让 str2 和 str3 排序,保证整体有序。
if (b>c)
{
temp = b;
b = c;
c = temp;
}
cout << a << b << c;
}
Const 常量
把程序中不允许改变值的变量定义为常变量
用 const 定义常变量必须同时初始化
C语言中常用 #define 命令来定义符号常量。
#define PI 3.145159
C++中,用 const 定义常变量
const float PI = 3.14159;
const修饰指针和引用的两种情况请看:指针 和 引用 – Skyshin34的博客
函数
函数参数
传递变量的名称(实参的副本)
传递是单向的,传给形参的是实参变量的值,形参和实参不是同一个存储单元
#include <iostream>
using namespace std;
void swap( int a, int b)
{ int temp;
temp=a;
a=b;
b=temp; // 实现a和b的值互换
}
int main( )
{ int i=3,j=5;
swap(i,j);
cout<<"i="<<i<<" "<<"j="<<j<<endl; // i和j的值未互换
return 0;
}
传递变量的指针
传给形参的是实参变量的地址,形参指针变量指向实参变量单元。
void swap( int *p1,int *p2)
{ int temp;
temp=*p1;
*p1= *p2;
*p2=temp;
}
int main( )
{ int i=3,j=5;
swap(&i, &j); // int* pi=&i; int* pj=&j; swap(pi, pj);
cout<<"i="<<i<<" "<<"j="<<j<<endl;
return 0;
}
传送变量的别名
传给形参的是实参变量的别名,此时形参和实参是同一个,作用和传递变量指针一致,但更安全,高效
void swap( int &a,int &b)
{ int temp;
temp=a;
a=b;
b=temp;
}
int main( )
{ int i=3,j=5;
swap(i,j);
cout<<"i="<<i<<" "<<"j="<<j<<endl;
return 0;
}
使用const引用参数是替代按值传参的最佳选择
// 按值传参:会复制参数,开销大
void printValueByValue(std::string s) {
std::cout << "按值传参: " << s << std::endl;
}
// const 引用传参:不复制参数,效率高且不允许修改
void printValueByConstRef(const std::string& s) {
std::cout << "const引用传参: " << s << std::endl;
}
函数的声明
C++中,如果函数调用的位置在函数定义之前,则要求在函数调用之前必须对所调用的函数作函数原型声明
和C#不同的点:可以声明不带参数名的函数,编译器只关心类型匹配
int max(int, int); // OK
int max(int x, int y); // OK
默认形参
可以给形参一个默认值,而不必一定要从实参取值。
float area(float r=6.5);//指定r的默认值为6.5
area( ); //相当于area(6.5);
若有多个形参,可以使每个形参有默认值,也可只对一部分形参指定默认值。由于实参与形参的结合顺序是从左至右,因此指定默认值的参数必须放在形参表列中的最右端(从右往左)
void f1(float a,int b=0,int c,char d=‘a’);//不正确
void f2(float a,int c,int b=0,char d=‘a’);//正确
作用域和作用域运算符
作用域是指标识符在程序中可以被引用的范围。每一个代码实体(变量、函数等)都有其有效的作用域,只能在变量的作用域内使用该变量,不能直接使用其它作用域中的变量。
C++提供作用域运算符“:: ”,它能指定所需要的作用域
-
X::Y
:表示在X
的作用域中访问Y
- 当
::
全局作用域是匿名作用域,前面什么都不写时,表示访问全局作用域中的标识符。
float a=13.5;
int main( ){
int a=5;
//在main函数中局部变量将屏蔽全局变量
cout<<a<<endl; //5
cout<< :: a<<endl; // 13.5
}
函数重载
C++中,允许在同一作用域中用同一函数名定义多个函数,这些函数的参数表(参数个数和类型)不同
内置函数 inline function
指定内置函数的方法:在函数首行的左端加一个关键字——inline
在程序调用这些函数时,并不是真正执行函数的调用过程(比如保留返回地址等),而是在编译时将所调用函数的代码嵌入到主函数中
内置函数与用#define命令实现的带参宏定义类似但不完全相同
对比项 | 内联函数(inline) | 带参宏定义(#define) |
---|---|---|
定义时机 | 编译时由编译器展开 | 预处理阶段展开(编译前),纯文本替换 |
是否语法检查 | ✅ 有完整的类型检查与语法检查 | ❌ 没有语法检查,可能导致难以发现的错误 |
本质 | 函数(有类型系统) | 宏(文本替换) |
替换方式 | 将函数体插入到调用处(编译器控制是否内联) | 简单的字符串替换,不做任何分析 |
使用限制 | 函数体要定义在头文件中 | 无明确限制(但易出错) |
适用场景 | 适合小型且频繁调用的函数 | 一般已被内联函数替代 |
运行速度 | ✅ 高(避免函数调用开销) | ✅ 也高(但不如内联函数灵活) |
示例 | inline int max(int a, int b) | #define MAX(a,b) ((a)>(b)?(a):(b)) |
项目 | 普通函数 | 内联函数 |
---|---|---|
调用机制 | 函数调用时发生跳转(压栈/出栈) | 不跳转,直接将函数代码替换进来 |
编译期 vs 运行期 | 运行期处理 | 编译期展开 |
适合什么情况 | 适合大函数、复杂逻辑 | 适合小而简单的函数(如一两行) |
缺点 | 有函数调用开销 | 频繁调用大函数会导致代码膨胀(体积变大) |
对类体内定义的函数被隐含地指定为内置函数,可以省略 inline。
课外:但很快我就想到会有性能问题。类内定义函数会被标为 inline
,但编译器为了避免“开销大”,有些不会真正展开,这正是编译器优化的职责。
动态创建对象 new & delete
通过这两个运算符允许程序员在程序中对任何基本数据类型或用户自定义的类型控制的内存进行分配与释放。这称为动态内存管理,由运算符new和delete完成。
动态内存针对的是指针类型
new运算符创建对象包括:首先分配对象需要的内存,然后调用构造函数初始化对象。
delete运算符删除对象包括:首先调用析构函数释放对象用到的资源,然后释放对象所占的内存。
new和delete运算符有new/delete和new []/delete []两种形式,它们需要对应使用。
new 运算符的一般格式:new 类型 [初值];
new int; // 开辟一个存放整数的空间,返回一个指向整型数据的指针
new int(100);// 开辟一个存放整数的空间,并指定该整数的初值为100
new char[10];// 开辟一个存放字符数组的空间,该数组有10个元素
new int[5][4];// 开辟一个存放二维整型数组的空间,该数组大小为5*4,返回一个指向字符数组的指针
float *p=new float(3.14159);// 开辟一个存放实数的空间,并指定该实数的初值为3.14159。 将返回的指向实型数据的指针赋给指针变量p
delete 运算符的一般格式:delete [ ] 指针变量;
delete p; // 撤销实数空间
delete[] pt;// 撤销数组空间
例1:动态建立并使用一个一维整数数组
int main()
{
//int [] marks = new int[5] { 99, 98, 92, 97, 95}; C#中的做法
int *p= new int [5]; //建立具有5个元素的动态整型数组
int sum=0,i;
for(i=0;i<5;i++)
{
*(p+i)=i;
sum+=*(p+i);
cout<<*(p+i)<<" ";
}
cout<<"\n"<<sum<<endl;
delete[] p; //删除p指针所指向的数组
}
int p = new int[5];不能这样写,new int[5]
返回的是一个 指向 int 的指针(int)*,试图将int*
赋值给 int
,类型不兼容
静态生成数组
int arr[5] = {1, 2, 3, 4, 5}; // 完全初始化
int arr[5] = {1, 2}; // 部分初始化,剩余默认为 0
项目 | 静态数组 | 动态数组 (new ) |
---|---|---|
定义方式 | int arr[5]; | int* p = new int[5]; |
存储位置 | 栈(stack) | 堆(heap) |
生命周期 | 随作用域自动释放 | 需要手动 delete[] |
长度是否可变 | ❌ 固定编译时已确定 | ✅ 可通过变量确定 |
初始化 | 可选、部分初始化自动填 0 | 默认不初始化(内容随机) |
性能 | 栈分配速度极快 |
此外堆上创建支持运行时动态类型判断(多态),或构建复杂数据结构
例2:栈上直接创建对象和堆上用 new
创建对象(动态分配)
for (int i = 0; i < 10; ++i) {
Student temp(i, "temp", 'M'); // 循环内临时对象,每次循环自动销毁
temp.display();
}
Student* createGlobalStudent() {
// 对象在堆上,函数返回后不会销毁
return new Student(1, "Global", 'F');
}
int main() {
Student* globalS = createGlobalStudent();
globalS->display();
// ...其他逻辑,对象一直存在...
delete globalS; // 手动释放
return 0;
}