C++——基础知识
一 初始化列表
int a=0;
int b(0);
int c=int();int d={};
int e{};
int f=int {};
为什么int b(0)不可以写为int b()
因为这样写和函数声明一样了 int add()。会导致歧义
可以直接在变量后加花括号
数组初始化:可以通过花括号直接初始化数组。如int arr[]={1,2,3,4,5};等价于int *p=arr
指针初始化限制:int *br[2]={1,2}。这是错误的。因为br被视为常量指针而不是数组
二 输入输出
1. C语言
基本语法:需要包含#include<stdio.h>,使用scanf和printf进行输入输出,如scanf("%d %c", &a, &ch);。
安全问题:scanf被认为不安全,建议使用scanf_s替代,否则会出现警告C4996。
格式要求:必须严格匹配格式控制符和变量类型,如%d对应整型,%c对应字符型。
2. C++
头文件:需要包含头文件#include <iostream>和命名空间using namespace std;
流对象:cin代表键盘输入流(标准输入设备),cout代表屏幕输出流(标准输出设备)
类型无关性:C++输入输出不需要指定变量类型,系统会自动处理
char ch;
int a;cin>>ch>>a;
cout<<"a="<<a<<"ch="<<ch<<endl;
输入:使用连续提取符>>
输出:连续插入符<<
换行符:endl相当于\n,用于换行输出
char str[20];
cin>>str;//把空格作为结束符
cout<<str<<endl;
当输入cindy beautiful 只会输出cindy。因为空格会被视为结束符
char str[20];
cin.getline(str,20);//最长为19,因为最后一个是\0用于结束
cout<<str<<endl;
当输入cindy beautiful可以输出全部
可以通过第三个参数指定结束符,如cin.getline(str, 20, '#');。
三 const与指针
1)const作为修饰符
- C语言中的const:
- 本质:修饰的变量称为"常变量",本质上仍以变量为主
- 限制:不能用于定义数组长度,如int arr[n]会报错(n为const变量)
- 示例:const int n=10; int arr[n]={1,2}; 在.c文件中编译错误
- C++中的const:
- 本质:以常量为主,编译器会进行常量替换在编译阶段,会把有关a的值全部替换
- 特性:可以用于定义数组长度,如int arr[n]={1,2,3,4}在.cpp文件中合法
- 内存机制:虽然可以取地址,但编译时会直接替换为字面值
2)const作为指针修饰符
C语言:通过指针可以修改const变量的值。C语言中const只是语法限制,内存仍可以修改
int* p = (int*)&n;
将指针p
指向了常量n
的地址,这里进行了强制类型转换,把n
的地址(原本是const int*
类型)转换为int*
类型。*p = 100;
通过指针p
修改了n
所占用内存位置的值,将其改为100
。- 输出都为100
C++ 打印时n显示原值5,遇见n用5替换掉在编译阶段
- 关键区别:
- C++:const是"真常量",编译时进行值替换,但取地址操作仍合法
- C语言:const是"只读变量",可通过指针绕过限制
3)const指针用法
int *p1=&a;普通指针
可以修改指向和值 eg:p1=&b; *p=100;
const int*p2=&a或 int const* p2=&a;指针所指向的值是常量(解引用)(值不可变)
声明了一个指向
const int
(常量整数)的指针p2
,并让p2
指向变量a
意味着被它修饰的对象的值不能被改变
不能通过p2修改a的值 (*p2=100是错误的)
可以修改p2的指向 (p2=&b合法)
int *const p3=&a; 指针本身是常量(指向不可变)
int *
:表示指针p3
指向int
类型的数据(即a
是int
变量)。const
:修饰的是 指针p3
本身,表示p3
是一个 常量指针(const pointer
),其指向一旦初始化后就 不能再改变。不能改变p3的指向
可以通过p3修改a的值
const int *const p4=&a;
即不能修改值也不能修改指向
4)const指针相互赋值问题
变量声明
int a = 10;
- 这行代码声明了一个名为
a
的整型变量,并初始化为10
。在内存中为a
分配了一块存储整数的空间,并且该空间中存储的值为10
。const int b = 20;
- 声明了一个名为
b
的const
整型变量,初始化为20
。const
关键字表示b
的值在初始化后不能被修改,这是一种常量声明方式,主要用于保护变量的值不被意外篡改 。指针声明与操作
int* p1 = &a;
- 声明了一个指向整型的指针
p1
,并将其初始化为变量a
的地址。通过这个指针,后续可以对a
进行间接访问,例如使用*p1
来读取或修改a
的值(因为a
不是常量 )。//int* p2 = &b; // *p2 = 200;
- 错误的。因为
b
是const int
类型,不能将const int
类型变量的地址直接赋值给普通的int*
类型指针。- 如果这样做,后续通过
p2
就可能会修改b
的值,这与b
作为常量的定义相违背。*p2 = 200;
更是会因为尝试修改const
变量的值而导致编译错误。const int* p2 = &b;
- 声明了一个指向
const int
类型的指针p2
,并将其初始化为b
的地址。这种指针类型保证了不能通过p2
来修改b
的值,符合b
作为常量的特性。但可以通过p2
来读取b
的值,例如printf("%d", *p2);
是合法的操作。int* p3 = (int*)&b;
- 这里通过强制类型转换,将
const int
类型变量b
的地址转换为int*
类型,并赋值给指针p3
。这种做法绕过了const
类型检查,虽然在语法上可行,但在语义上是不安全的,因为b
原本是const
类型,不应该被修改。*p3 = 100;
- 通过指针
p3
修改了b
的值(尽管b
被声明为const
)。由于前面的强制类型转换绕过了const
限制,编译器不会报错(在运行时修改了原本应该是常量的b
的值 ),但这种操作破坏了const
的语义,是不推荐的编程行为。
四 引用
4.1 引用详解
1. 定义
*和变量名结合
类型&引用变量名称=变量名称
定义引用时必须要初始化,没有空引用。没有引用的引用
&和类型结合称为引用符号,不是取地址符,代表别名的意思
int const& c = a;
:声明一个 const
整型引用 c
并绑定到 a
,const
修饰引用意味着不能通过 c
修改 a
的值 ,// c+=100; // error;
被注释掉是因为尝试通过 c
修改 a
会导致编译错误。
2. 左值和右值
左值具有取地址能力,可以被赋值。&
右值不能取地址。通常是字面常量或临时变量。&&
常量左值引用绑定右值时编译器会创建临时变量且不能通过引用修改值;右值引用绑定右值后可对引用进行修改(在引用不是 const
的情况下 )。
const int &rb=a;万能引用,只能通过引用获取值不能修改值。即可以左值也可以右值
4.2 引用与指针的区别
1. 定义本质
引用是已经存在变量的别名,不占用额外内存空间。本质上和所引用的变量共享同一块内存。可以直接使用对象的名字来访问所引用的对象,无需解引用操作
指针存地址是一个独立的变量,其值为 另一个变量的内存地址。占用一定的内存空间。在32位中占4字节,64位中8字节
2. 初始化与绑定
引用:声明时必须初始化int &p=a,但一旦绑定变量,无法再绑定其他变量
指针:声明时可以初始化或空值int *p=nullptr,指针变量的值可以发生改变,存储不同实例
3. 空值与安全性
引用:无空引用,绑定后始终指向有效变量
指针:允许为空。所以在作为形参时要判断合法性
4. 内存与操作
引用:无独立内存,无多级引用
指针:占独立内存,支持指针运算,有多级指针
5.sizeof取大小
引用:对引用变量使用sizeof得到的是变量的大小
指针:使用sizeof得到指针变量的大小
6. ++操作
引用:对引用的操作直接反应到所引用的实体上/变量/对象
指针:对指针变量的操作会使得指针指向下一个实体(变量)的地址。而不是改变所指实体的内容
问题:函数传参时,什么时候用指针,什么时候用引用?
需要处理空值,允许参数无意义或动态改指向——用指针 void func(int*p)
确保参数非空,简化语法——void func(int &p)常搭配const避免修改void func (const int&p)
4.3 引用的使用规则
- 必须初始化,且不能重新绑定。
- 不能为
nullptr
,必须始终指向有效对象。 - 类型严格匹配,除非涉及隐式转换或基类引用。
- 左值引用绑定左值(非常量引用需可修改),右值引用绑定右值。
- 避免返回局部变量的引用,防止悬空引用。
五. 函数重载
函数名相同,参数类型和个数不同
1. 编译式多态
函数重载是编译时的多态,编译器根据实参和形参时的不同在编译阶段进行匹配
识别方法:c++通过函数原型(返回类型+函数名+参数列表)而非函数名来区分函数
2. 名字粉碎技术
编译器将函数名、参数类型、返回类型等信息重新编码的机制,使源码相同函数名在目标文件中具有不同标识。
六. 函数模板
template<模板参数表>//类型
返回类型 函数名(形式参数)
{//函数体
}
template <class T> //<typedef T>
T Max(T a, T b)
{return a>b?a:b;
}
模板推演过程:编译器为每种使用的类型生成特定函数
底层实现类似于typedef int T
.cpp → 预编译 → .i → 编译 → .s → 汇编 → .obj → 链接 → .exe。模板推演发生在编译和汇编阶段
七. 名字空间
- 局部域(函数域):在函数中定义的变量,超出函数范围失效
- 全局域:在函数外定义的变量,从定义处开始向下都有效
- 块域:在代码块(如{})中定义的变量,超出块范围失效
- 类域:在class中定义的数据成员,仅在类作用域内有效
- 核心作用:解决全局变量名、函数名和类名的命名冲突问题
- 定义规则:
- 只能在全局域中定义
- 不能在函数、类或块中定义命名空间
- 可以嵌套定义命名空间
- 使用方式:
- 作用域解析符"::"访问命名空间成员
- 语法:命名空间名::成员名
- 示例:YangHeping::func()
- 注意事项:
- 不推荐使用using namespace全局引入
- 工业代码中通常显式指定命名空间
- 可以避免命名污染问题
八. new/delete 关键字
int *p=new int(10);
1. 自动计算new后面类型大小 sizeof(int)。与malloc不同,new不需要手动使用sizeof计算类型大小
2. malloc 根据计算出来的大小申请内存空间
3. 对申请的空间进行初始化,初始化为括号里的值
4. 将地址返回给p。new返回的指针类型与申请的类型严格匹配,不需要像malloc那样进行强制类型转换
2 new和malloc的区别
1. new/delete是运算符,malloc/free是库函数
2. malloc申请空间时需要手动计算大小,new只需要类型名自动计算大小
3. malloc申请的空间不会初始化,new可以初始化
4. malloc返回值为void*,接收时必须强转,new不需要
5. malloc申请空间失败时会返回NULL,使用时必须判空,new会抛出异常,所以要有捕获异常处理程序。
delete
- 内存释放本质:
- delete p释放p指向的堆内存,而非指针变量本身
- 指针变量本身的生命周期由其作用域决定
- 悬空指针风险:
- delete后指针仍保留原地址值,形成"悬空指针"
- 类比离婚后仍保留钥匙可能引发安全问题
- 最佳实践:
- delete后应立即将指针置为NULL
- 避免通过悬空指针访问已释放内存
int *p=new int[n] {1,2,3,4,5} 【n】申请连续空间
delete [] s;释放一组空间
- 初始化能力:
- malloc只能通过memset或循环手动初始化
- new支持多种初始化方式:
- new int(10):单个空间初始化为10
- new int[n]{}:连续空间初始化为全0
- new int[n]{1,2,3,4}:前4个元素初始化,其余为0
栈:由操作系统自动管理,存储局部变量。内存分配和释放速度快,空间有限
堆:程序员手动管理,用于动态分配内存。空间较大,分配释放速度慢
new申请空间,p存在栈上,但p指向的对象在堆上
malloc只能从堆分配内存。无法在栈上分配
九 c++11新特性
auto定义的变量,可以根据初始化的值,在编译时推导出变量名的类型
auto
推导指针时,只关注指针指向的类型,除非用auto&
推导引用,才会保留指针本身的const
等限定。- 常量指针(
int* const
)的const
是修饰 “指针本身”,auto
直接推导值时会 “跳过”,但推导引用时会保留
- 推导时机:编译时根据初始化值确定类型
- 必须初始化(如auto a;会编译错误)
- 如auto x = 5推导为int,auto dx = 5.0推导为doub
- 类型识别原则:
- 整型:直接数字(如12)
- 浮点型:带小数点(如12.23默认double,加f后缀为float)
- 字符型:单引号包裹(如'1'的ASCII值为49)
-
- 指针类型:
- auto *ip = &x中auto推导为int,ip为int*
- auto xp = &x直接推导为int*(auto不带*也可推指针)
- 引用类型:
- auto &cx = x中auto推导为int,cx为int&
- auto d = cx会抛弃引用属性,d为int(值拷贝)
- 指针类型:
-
- 两条核心规则:
- 非指针/引用时:抛弃const和引用属性(如auto f = e,e为const int但f为int)
- 指针/引用时:保留const属性(如auto &h = g,g为const int&则h也是)
- 典型场景:
- const auto &crx = x推导为const int&
- auto rx = crx抛弃const和引用,rx为int
- auto &rx = crx保留const,rx为const int&
- 两条核心规则:
-
- 模板式推导:
- func(auto x)会根据实参类型实例化(类似模板)
- 传递数组时推导为数组类型(如int[5]),sizeof得到整个数组大小
- 限制:
- 不能用于非静态成员变量
- 不能直接定义数组(如auto arr[] = {1,2,3}错误)
- 模板式推导:
decltype
推导表达式类型,但不实际计算表达式的值
int x = 10;
decltype(x) y = 20; // int(x 是 int)
decltype(x + y) z = 30; // int(x+y 结果是 int)
for循环:
for (声明 : 范围表达式) {// 循环体
}
- 声明:定义一个变量,其类型与范围中的元素类型匹配(通常用
auto
自动推导)。- 范围表达式:可以是数组、容器、初始化列表或任何定义了
begin()
和end()
成员函数的对象。
int arr[] = {1, 2, 3, 4, 5};
for (auto element : arr) {std::cout << element << " "; // 输出:1 2 3 4 5
}