C++开发基础
C++ 概述
C++ 是一种通用的、高性能的编程语言,由 Bjarne Stroustrup 于 1985 年在贝尔实验室开发。它是 C 语言的扩展,支持面向对象编程(OOP)、泛型编程和低级内存操作,广泛应用于系统软件、游戏开发、嵌入式系统和高性能计算等领域。
C++与C语言的区别与联系
语言特性与设计思想
C语言是面向过程的编程语言,核心思想是通过函数模块化解决问题。C++在C语言基础上扩展,支持面向对象编程(OOP),引入了类、继承、多态等特性,同时保留了C语言的底层操作能力。
C++兼容大部分C语法,但并非完全兼容。例如,C++中struct默认成员为public,而C语言需要显式声明;C++对类型检查更严格,禁止隐式类型转换。
核心区别
1. 面向对象支持
C++提供类(class)、封装、继承(单继承/多继承)、多态(虚函数)等OOP特性。C语言仅能通过结构体和函数指针模拟部分OOP功能。
2. 标准库差异
C++标准库(STL)包含容器(vector、map)、算法(sort、find)和智能指针等高级工具。C语言标准库更基础,如stdio.h、stdlib.h。
3. 内存管理
C语言使用malloc/free手动管理内存。C++引入new/delete运算符,支持构造/析构函数自动调用,并可通过RAII(资源获取即初始化)模式减少内存泄漏风险。
4. 异常处理
C++支持try-catch异常机制,C语言通常通过返回错误码或setjmp/longjmp处理异常。
5. 函数特性
C++支持函数重载、默认参数、内联函数(inline)和模板函数(泛型编程)。C语言不允许函数重载,且缺乏模板支持。
具体列子:
C++的初始化
#include<iostream.h>
using namespacing std;
int main(){
int a=10;
int b(10);
int c{10};
}
C++语言可以使用{}进行初始化
输入输出
C++中使用cin和cout进行输入输出
#include<iostream>
using namespacing std;
int main(){
int a;
int b;
char c;
cin>>a>>b>>c;
cout<<a<<b<<c<<endl;//endl相当于'\n'
return 0;
}//cin是标准输入流对象,cout是标准输出流对象,>>为提取符,<<为插入符

其中错误用法为:
cin>>a,c;
cout<<a,c<<endl;
原因是因为再解析的时候逗号会被解析成逗号运算符,
在使用cin和cont时必须包含<iostream>头文件,以及std命名空间
<iostream> 是 C++ 标准库中负责输入输出的头文件,包含 cin(标准输入流)和 cout(标准输出流)等对象的声明。若不包含该头文件,编译器无法识别这些对象,导致编译错误。例如:
命名空间 std 的必要性
C++ 标准库的组件(如 cin、cout)均定义在 std 命名空间中。直接使用 cout 或 cin 会导致名称解析失败,必须通过 std:: 前缀或 using 指令显式指定命名空间.
const关键字
举个例子:
#include<stdio.h>
int main(){
const int a = 10;
int *p = (int*)&a; // C语言允许这种强制类型转换
*p = 20; } // 实际行为未定义,但语法上可通过编译
因此我们可以得出结论,C语言中的const主要以变量为主,而在C++中主要以常量为主
#include<iostream>
using namespace std;
int main() {
int a = 10,b=20;
int* p1 = &a;
const int* p2 = &a;//常量指针,指向常量的指针
*p2 = b;//报错,因为p2指向的是一个常量不能被更改
int* const p3 = &b;//指针常量,指针的本身是一个常量
p3= &a;//报错,因为p3的指针本身为常量所以不能修改指向
}
Const,define,typedef的区别
define:在预处理阶段,仅仅只是进行简单的文本替换,没有类型检查,没有作用域,不分配内存
const:在编译和运行阶段干活,是带类型的只读变量,有类型检查并且会分配内存
typedef:在编译阶段进行,相当于给类型其别名不分配内存
常变量与指针
#include<iostream>
using namespace std;
int main(){
const int a=10;
int* const p2 =&a;//error
int *p=&a;//error,二者的原因都是因为破坏了常变量的不可修改性
//假设语句可执行,那么就可以通过p修改a的值,但是a常变量不可被修改所以出现报错
const int *p1 =&a;//不报错
//因为这个指针指向的是一个常量,无法破坏常量的不可修改性
const int* const p3 =&a;//不报错
return 0;
}
规则:可以将能力强的指针赋值给能力弱的指针:
#inclued<iostream>
using namespqce std;
int main(){
int a=10;
int *p=a;
int * const p1=p;//无报错
const int *p2=p;//无报错
const int* const p3=p;//无报错
return 0;
}
引用(别名)
定义:类型& 引用变量名 =变量名称
&与类型结合成为引用的符号,引用也就是别名的意思
引用的特点
-
必须初始化
引用在声明时必须绑定到一个已存在的变量,不能像指针那样先声明后赋值。 -
不可重新绑定
引用一旦初始化后,无法更改为其他变量的别名。 -
无空引用
引用必须指向有效的对象,不存在“空引用”的概念(如nullptr之于指针)。 -
自动解引用
使用引用时无需像指针那样显式解引用(*操作符),直接通过引用名访问原变量。
#include<iostream>
int main(){
int a=10;
int& b=a;
int &x;//报错,引用必须初始化
int &y=NULL;//报错,引用没有空引用
int &&c =b;// 报错没有引用的引用
return 0;
}
与const一样,引用不能扩大原本变量的功能
如
#include<iostream>
using namespace std;
int main(){
int a=10;
const int b=20;
const int& x = b;//ok
int &y = b;//error
const int& z = x;//ok
return 0;
}
引用作为形参代替指针
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include <iostream>
#include<assert.h>
using namespace std;
// 使用指针交换两个整数的值
// 输入参数:指向两个整数的指针
// 函数内部使用断言确保指针不为空,防止空指针解引用
void Swap_Int(int* pa, int* pb) {
assert(pa != NULL && pb != NULL);//使用指针时,第一步需要判空
int tmp = *pa; // 经典三杯水
*pa = *pb;
*pb = tmp;
}
// 使用引用交换两个整数的值
// 输入参数:两个整数的引用
// 直接操作引用等同于操作原始变量
void Swap(int& ra, int& rb) {//经典三杯水
int tmp = ra; // 暂存ra引用的值
ra = rb; // 将rb引用的值赋给ra引用的变量
rb = tmp; // 将暂存的值赋给rb引用的变量
}
int main()
{
int a = 10, b = 20;
int& ra = a; // 定义a的引用ra
cout << "a=" << a << "b=" << b << endl; // 输出交换前的值
Swap(a, b); // 使用引用方式交换a和b的值
cout << "a=" << a << "b=" << b << endl; // 输出第一次交换后的值
Swap_Int(&a, &b); // 使用指针方式再次交换a和b的值
cout << "a=" << a << "b=" << b << endl; // 输出第二次交换后的值
return 0;
}
结果如图所示:

指针与引用的区别
从语法规则上来讲,指针变量存放某个实例的地址,而引用时某个实例的别名.
程序为指针分配内存空间而不为引用分配内存空间
指针的值可以发生改变,用来存储不同实例的地址,而引用在被定义的时候就被初始化,之后无法改变
指针变量的值可以为空,而引用不能为空
对指针sizeof得到的是指针变量的大小,而对引用sizeof得到的是变量的大小
从汇编上理解引用
源码如下
#include<iostream>
int main(){
int a=10;
int* ip =&a;
int &b =a;
*ip =100;
b=200;
return 0;
}
mov:mov ebx 0x0012ff7z 直接寻址
lea(取地址指令):有效地址的加载指令,计算机原操作数据的内存地址,存入目的寄存器
eax:累加寄存器,主要用于算数计算
在汇编层的眼中引用本身是占内存的,其所占的空间与指针一样大,而一引用在汇编层上与指针常量非常相似,二者都必须初始化,而且一旦绑定就不可更改,并且不能为NULL,但是不同的是引用他会自己解引用的指针常量.
将引用作为函数的返回值
例子:
int& fun(int& a)
{
a = 10;
return a;
}
int main()
{
int a = 1;
int& b = fun(a);
}
这样因为返回的是一个引用也就是一个别名,并不产生临时对象
注意:正确返回的东西只有:
1.全局变量
2.static静态变量
3.传入的引用或者指针
4.类的成员变量
5.new/malloc出来的堆变量
eg.
int& fun()
{
int a =10;
return a;
}
int main()
{
int c = 10;
int& b = fun();
}
这时我们返回的局部变量a但是a会在fun调用结束后销毁,导致引用悬空出现危险.
函数的调用过程
在函数调用过程中参数从右向左依次压入栈中,2.将函数调用的下一条指令地址压入栈中3.跳转执行函数,4.建立栈帧将旧的基址指针edp压栈保存,令edp=esp移动esp为局部变量分配栈空间,通过edp的偏移量访问参数和局部变量
int add(int a,int b)
{
return a + b;
}
int main()
{
int a = 1;
int b = 2;
int c = add(a,b);
}

cdecl与stdecl的区别
cdecl与stdecl是两种函数的调用模式,二者参数的入栈顺序都是从右往左,
但是,cdecl由调用者清理栈,(C语言默认/Linux/window通用),支持可变参
而stdecl则由被调用者清理栈,不支持可变参,
为什么会有这样的区别呢?
因为cdecl在函数调用完成后,由调用者清理,调用者知道自己传了几个参数,所以支持可变参数
但是stdecl必须要知道参数有几个字节,要是随便传就不知道要清理什么了
就像去饭店吃饭:cdecl是吃完了自己收拾桌子,因为是自己收拾桌子所以可以随便点菜,
而stdecl是饭店同一收拾,为了高效的清理,就只能每一个桌子上一样的菜,就不能随便点菜了,
内联函数
简单来讲内联函数就是将函数的代码直接复制粘贴到调用的地方不像普通函数需要跳转执行拥有出栈压栈的开销,内联函数在编译时展开,适合代码短被频繁调用的函数,如果代码过长编译器会拒绝内联
eg.普通函数
int add(int a,int b)
{
return a + b;
}
int main()
{
int a = 1;
int b = 2;
int c = add(a,b);
}
内联函数
inline int add(int a,int b)
{
return a + b;
}
int main()
{
int a = 1;
int b = 2;
int c = add(a,b);
}
在编译后c会直接变成c = 1 + 2;
左值和右值
左值和右值是C++中表达式的两种基本分类,用于描述表达式的值类别(value category)。它们的核心区别在于能否被赋值或取地址,以及生命周期管理方式。
左值(LValue)
表示一个具名的、持久存在的对象或内存位置。
可以出现在赋值运算符的左侧(即可以被赋值)。
可以取地址(例如&x)。
通常是变量、函数名、返回左值引用的函数调用等。
右值(RValue)
表示临时的、短暂存在的值,通常没有持久的内存地址。
只能出现在赋值运算符的右侧(不能被赋值)。
不能取地址(例如&42是非法的)。
包括字面量(如42)、临时对象、返回非引用类型的函数调用等。
简而言之,就是可以取地址的值就是左值,在C++11中普通引用只能帮左值,&&右值引用可以用来绑定右值.const 左值引用(const T&):可以绑定到左值 / 右值(C++ 标准特意放宽的规则)
为什么需要右值引用?
右值引用的核心目的不是「绑定字面量」,而是:
- 接管临时对象的资源(移动语义,比如
std::string&& s = get_temp_string();); - 避免拷贝,提升性能;
- 实现完美转发(函数模板中
std::forward)。
strcpy,sprintf,memcpy的区别
三者都为拷贝函数但是拥有着显著的差别,
strcpy只用来拷贝字符串遇到\0才停下来
strcpy(buf,str);
memcpy(万能拷贝):按照字节拷贝n个,不管内容是什么,不受\0的影响
memcpy(buf,str,n);
sprintf(buf,const char*):功能类似printf只不过是将输出输出到buf数组,它可以将数字,字符,字符串拼接到一起,并且自动在结束处加\0让其成为合法字符串
更多推荐


所有评论(0)