C++基础语法与面向对象特性
一.C++基础语法
1.相较于C语言的一些改变
接下来会给出一段简单的程序来展示C++上手与C语言的不同之处
#include<iostream>
using namespace std;int main()
{int a,b;cin>>a>>b;cout<<a+b<<endl;return 0;
}
1.头文件包含#include<iostream>
#include<iostream>是C++中的标准输入输出库。
1. 设计理念
C++ (
iostream
):面向对象设计,使用流 (Stream) 概念(如cin
,cout
),支持运算符重载和类型安全。C (
stdio.h
):面向过程设计,基于文件指针(如stdin
,stdout
),依赖格式化字符串(如%d
,%s
)。
2. 核心组件对比
功能 | C++ (iostream ) | C (stdio.h ) |
---|---|---|
标准输入 | std::cin | scanf() , fgets(stdin, ...) |
标准输出 | std::cout | printf() , puts() |
错误输出 | std::cerr (无缓冲) | fprintf(stderr, ...) |
日志输出 | std::clog (有缓冲) | 无直接等价物 |
格式化控制 | <iomanip> (如 setw , hex ) | 格式字符串 (如 "%5d" , "%x" ) |
类型安全 | ✅ 编译时类型检查 | ❌ 易出现格式化错误(如 %d 传 float ) |
扩展自定义类型 | ✅ 重载 << 和 >> | ❌ 需手动转换 |
3.endl标识符
endl标识符用于C++的流输出操作。它主要完成两个功能:插入换行符\n,刷新缓冲区,强制将缓冲区的内容输出到屏幕上。
2.命名空间namespace以及using关键字
命名空间的设计是为了解决项目中的命名冲突。
#include<iostream>void swap(int &a,int &b)
{int temp;temp=a;a=b;b=temp;
}int main()
{int a,b;std::cin >> a >> b;swap(a,b);std::cout<<a<<b<<std::endl;return 0;
}
如上的交换函数swap(),在库中也实现了同名函数,但若不指定是std库中的,会默认调用当前作用域的swap()函数。main()中的std::cout便是指定了作用域的流输出函数,我们可以用::操作符展开命名空间域中的部分成员,而该操作也可以通过using关键字实现
namespace wjh {void swap(int& a, int& b){int temp;temp = a;a = b;b = temp;}}int main()
{int a,b;std::cin >> a >> b;wjh::swap(a,b);std::cout<<a<<b<<std::endl;return 0;
}
另外,不同文件或者相同文件的同名命名空间域会自动合并为同一个
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
namespace bit
{
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST* ps, int n);
void STDestroy(ST* ps);
void STPush(ST* ps, STDataType x);
void STPop(ST* ps);
STDataType STTop(ST* ps);
int STSize(ST* ps);
bool STEmpty(ST* ps);
} /
/ Stack.cpp
#include"Stack.h"
namespace bit
{
void STInit(ST* ps, int n)
{
assert(ps);
ps->a = (STDataType*)malloc(n * sizeof(STDataType));
ps->top = 0;
ps->capacity = n;
} /
/ 栈顶
void STPush(ST* ps, STDataType x)
{
assert(ps);
// 满了, 扩容
if (ps->top == ps->capacity)
{
printf("扩容\n");
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity
* 2;
STDataType* tmp = (STDataType*)realloc(ps->a,
newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
ps->a = tmp;
ps->capacity = newcapacity;
} p
s->a[ps->top] = x;
ps->top++;
} /
/...
} /
/ Queue.h
#pragma once
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
namespace bit
{
typedef int QDataType;
typedef struct QueueNode
{
int val;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
// ⼊队列
void QueuePush(Queue* pq, QDataType x);
// 出队列
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
bool QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);
}
// Queue.cpp
#include"Queue.h"
namespace bit
{
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
} /
/ ...
} /
/ test.cpp
#include"Queue.h"
#include"Stack.h"
// 全局定义了⼀份单独的Stack
typedef struct Stack
{
int a[10];
int top;
}ST;
void STInit(ST* ps){}
void STPush(ST* ps, int x){}
int main()
{
// 调⽤全局的
ST st1;
STInit(&st1);
STPush(&st1, 1);
STPush(&st1, 2);
printf("%d\n", sizeof(st1));
// 调⽤bit namespace的
bit::ST st2;
printf("%d\n", sizeof(st2));
bit::STInit(&st2);
bit::STPush(&st2, 1);
bit::STPush(&st2, 2);
return 0;}
3.缺省参数
对于函数的传参,可以分为半缺省或全缺省。传参过程中若有部分或者全部值未显式给出,会使用给出的缺省参数。
void Print(int a = 2, double b = 3.14) {std::cout << a << " " << b << std::endl;
}
int main()
{Print();Print(5);Print(5, 6.7);return 0;
}
以上用例的输出结果为
值得注意的是, 缺省参数应从右向左缺省,传参数为从左向右传参,不可间隔传参。对于半缺省函数,不可不传参,否则无法通过预编译(以下为错误示范)
void Func2(int a, int b = 10, int c = 20)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl << endl;
}
int main()
{Func2();return 0;
}
4.函数重载
函数重载指的是:对同一作用域的两个同名函数,若他们的参数不同(个数,顺序,类型)就构成函数重载。若只有返回值不同,不构成重载。函数重载是面向对象多态性的一个重要体现
#include<iostream>
using namespace std;
// 1、参数类型不同
int Add(int left, int right)
{
cout << "int Add(int left, int right)" << endl;
return left + right;
} d
ouble Add(double left, double right)
{
cout << "double Add(double left, double right)" << endl;
return left + right;
} /
/ 2、参数个数不同
void f()
{
cout << "f()" << endl;
} v
oid f(int a)
{
cout << "f(int a)" << endl;
} /
/ 3、参数类型顺序不同
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
} v
oid f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
// 返回值不同不能作为重载条件,因为调⽤时也⽆法区分
//void fxx()
//{}
//
//int fxx()
//{
// return 0;
//}
值得注意的是,当两个同名函数其中一个参数为缺省,也构成重载。但是会出现歧义,编译无法通过
// 下⾯两个函数构成重载
// f()但是调⽤时,会报错,存在歧义,编译器不知道调⽤谁
void f1()
{
cout << "f()" << endl;
}
void f1(int a = 10)
{
cout << "f(int a)" << endl;
}
int main()
{
Add(10, 20);
Add(10.1, 20.2);
f();
f(10);
f(10, 'a');
f('a', 10);
return 0;
}
5.引用及其特性
引用就是为一个自定义变量或内置类型变量起一个别名,如:狮子头林冲,及时雨宋江...显然对引用的修改就是对被引用的修改,不需要像指针一样为别名另外开辟一个空间
#include<iostream>
using namespace std;
int main()
{
int a = 0;
// 引⽤:b和c是a的别名
int& b = a;
int& c = a;
// 也可以给别名b取别名,d相当于还是a的别名
int& d = b;
++d;
// 这⾥取地址我们看到是⼀样的
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
return 0;
}
1.引用定义时必须初始化,一个变量可以有多个引用
#include<iostream>
using namespace std;
int main()
{
int a = 10;
// 编译报错:“ra”: 必须初始化引⽤
//int& ra;
int& b = a;
int c = 20;
// 这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,
// 这⾥是⼀个赋值,相当于修改引用值
b = c;
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
return 0;
}
2.函数传参和返回值使用引用可减少拷贝
并且对引用值的修改可以同时实现对被引用值的修改,这点类似指针的部分功能
void Swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
} i
nt main()
{
int x = 0, y = 1;
cout << x <<" " << y << endl;
Swap(x, y);
cout << x << " " << y << endl;
return 0;
}
引用传参或者返回值的优点会在传入自定义类型时明显展示,减少了拷贝造成的额外开销,用C++实现栈为例:
#include<iostream>
using namespace std;
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST& rs, int n = 4)
{
rs.a = (STDataType*)malloc(n * sizeof(STDataType));
rs.top = 0;
rs.capacity = n;
} // 栈顶
void STPush(ST& rs, STDataType x)
{
assert(ps);
// 满了, 扩容
if (rs.top == rs.capacity)
{
printf("扩容\n");
int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;
STDataType* tmp = (STDataType*)realloc(rs.a, newcapacity *
sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
rs.a = tmp;
rs.capacity = newcapacity;
}
rs.a[rs.top] = x;
rs.top++;
} // int STTop(ST& rs)
int& STTop(ST& rs)
{
assert(rs.top > 0);
return rs.a[rs.top];
} i
nt main()
{
// 调⽤全局的
ST st1;
STInit(st1);
STPush(st1, 1);
STPush(st1, 2);
cout << STTop(st1) << endl;
STTop(st1) += 10;
cout << STTop(st1) << endl;
return 0;
}
3.与指针的一些对比
sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下
占4个字节,64位下是8byte)
特性 | 引用 | 指针 |
---|---|---|
初始化要求 | 必须初始化 | 可声明为空指针(nullptr) |
重绑定 | 不可更改绑定对象 | 可随时改变指向对象 |
内存占用 | 无独立内存(别名) | 占用独立内存存储地址 |
访问方式 | 直接使用(隐式解引用) | 需显式解引用(*ptr) |
空值安全性 | 天然防空(不能绑定null) | 可能产生空指针异常 |
多级间接访问 | 不支持 | 支持多级指针(int**) |
操作符 | 无特殊操作符 | 支持取址(&)、解引用(*)等 |
参数传递语义 | 明确表达修改意图 | 需配合const表达只读意图 |
4.const引用
可以引⽤⼀个const对象,但是必须⽤const引⽤。const引⽤也可以引⽤普通对象,因为对象的访问权限在引⽤过程中可以缩⼩,但是不能放⼤。
需要注意的是类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样⼀些场景下a*3的和结果保存在⼀个临时对象中, int& rd = d 也是类似,在类型转换中会产⽣临时对象存储中间值,也就是说,rb和rd引⽤的都是临时对象,⽽C++规定临时对象具有常性,所以这⾥就触发了权限放⼤,必须要⽤常引⽤才可以。所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,C++中把这个未命名对象叫做临时对象
int main()
{
const int a = 10;
// 编译报错:error C2440: “初始化”: ⽆法从“const int”转换为“int &”
// 这⾥的引⽤是对a访问权限的放⼤
//int& ra = a;
// 这样才可以
const int& ra = a;
// 编译报错:error C3892: “ra”: 不能给常量赋值
//ra++;
// 这⾥的引⽤是对b访问权限的缩⼩
int b = 20;
const int& rb = b;
// 编译报错:error C3892: “rb”: 不能给常量赋值
//rb++;
return 0;
}#include<iostream>
using namespace std;
int main()
{
int a = 10;
const int& ra = 30;
// 编译报错: “初始化”: ⽆法从“int”转换为“int &”
// int& rb = a * 3;
const int& rb = a*3;
double d = 12.34;
// 编译报错:“初始化”: ⽆法从“double”转换为“int &”
// int& rd = d;
const int& rd = d;
return 0;
}