正片开始
C++与C动态内存申请的差异性
C++中的动态内存申请与C语言中的动态内存申请类似,都需要使用关键字 new 或 malloc 以及相应的类型占位符来分配内存空间,并使用关键字 delete 或 free 来释放内存。但是二者之间也存在一些区别,具体如下:
- 内存分配方式不同
- C++ 使用 new 操作符从自由存储区分配连续的内存空间
- C 语言使用 malloc 函数从堆上分配一块指定大小的内存空间
- 内存释放方式不同
- C++ 中使用 delete 操作符释放先前用 new 分配的内存空间
- 释放单个变量内存语法:
delete 变量名; - 释放一段内存基本语法
delete [ ] 变量名;
- 释放单个变量内存语法:
- C++ 中使用 delete 操作符释放先前用 new 分配的内存空间
- C语言中使用 free 函数释放先前用 malloc 分配的内存空间
- 内存类型安全性不同:
- C++ 中通过使用 new 和 delete 来进行内存分配和释放,其管理机制更加安 全、便捷,并且不需要手动做开辟失败的处理,如果开辟失败C++内部会进行中断提醒,不用担心用到空指针的问题
- C语言中使用 malloc 和 free 时需要对类型进行手动转换,且在对内存进行操作时较容易出现类型错误。
动态内存申请
开辟单个变量内存
//开辟单个内存不做初始化的写法
int*p=new int;
//开辟单个内存并且做初始化的写法
int*p1=new int(1); //此时*p1为1
//释放单个变量
delete p;
delete p1;
开辟连续内存
//开辟连续内存不做初始化的写法
int*p=new int[5];
//开辟连续内存并且做初始化的写法
int*p1=new int[5]{1,2,3,4,5};
//释放连续内存
delete[] p;
delete[] p1;
在已开辟的内存中开辟内存
#include<iostream>
using namespace std;
struct Info
{
int data;
char name[15];
int num;
};
void test()
{
//这些开辟连续的大内存
char* str = new char[1024];
//再在这段内存中开辟一段数组
int* arr = new(str) int[10];
//再在这段内存中数组后面开辟字符串
char* s = new(str + sizeof(int) * 10) char[100];
//再在这段内存中字符串后面开辟结构体
Info* info = new(s + sizeof(s)) Info;
//接下来我们来用一下已开辟好的内存
cout << "数组内容:";
for (int i = 0; i < 10; i++)
{
//先赋值
arr[i] = i + 1;
//打印
cout << arr[i] << " ";
}
cout << endl;
//先对char数组赋值
strcpy(s, "你是大帅比吗?");
//打印
cout << s << endl;
//给结构体赋值
info->data = 1;
strcpy(info->name, "原来我是大帅比啊!!!");
info->num = 1000;
//打印
cout << "结构体内容:" << "data:" << info->data << " " << "name:" << info->name << " " << info->num << endl;
//释放
delete[] str;
}
int main()
{
test();
return 0;
}
输出结果
数组内容:1 2 3 4 5 6 7 8 9 10
你是大帅比吗?
结构体内容:data:1 name:原来我是大帅比啊? 1000
注意:如果刚开始开辟了一个连续大内存,然后再在这块大内存内部开辟几块小内存,释放内存时只需要释放这块大内存即可,无需释放小内存
函数封装思想开辟内存
#include<iostream>
using namespace std;
int* test(int size)
{
int* p = new int[size] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
return p;
}
void test(int*& arr1,int size)
{
arr1 = new int[size] {5, 5, 5, 5, 5, 5, 5, 5, 5, 5};
}
//打印函数
void print(int* arr, int size)
{
cout << "数组内容:";
for (int i = 0; i < size; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
int main()
{
//第一种方式通过函数开辟好内存将地址返回
int* arr = test(10);
//打印
print(arr,10);
//-------------------------------
//第二种方式,在外面定义指针变量,通过左值引用,改变外面的指针,让其指向动态内存
int* arr1 = nullptr;
test(arr1, 10);
print(arr1, 10);
//-------------------------------
//释放
delete[] arr;
delete[] arr1;
return 0;
}
C++类型转换
C++ 中的类型转换分为隐式类型转换和显式类型转换两种方式,所有显式类型转换基本语法:类型转换操作符<要转换的类型>(转换目标)
隐式转换
隐式类型转换: 是指程序自动将一种数据类型转换为另一种数据类型,而程序员不需要进行任 何操作。 例如:整型数据可以自动转换为浮点型数据显示转换
显式类型转换:是指程序员通过强制转换的方式将一种数据类型转换为另一种数据类型。在C++ 中,显示类型转换有以下几种方式- 静态转换 (static_cast)
- 原始转换(reinterpret_cast)
- 常量转换 (const_cast)
- 动态转换 (dynamic_cast)
静态转换
#include<iostream>
using namespace std;
int main()
{
int m = static_cast<int>(1.11);
cout << "m=" <<m<< endl;
return 0;
}
输出结果
m=1
原始转换
#include<iostream>
using namespace std;
void test()
{
printf("数字转换成函数地址,转换成功!\n");
}
int main()
{
//因为在64位机器上,地址占8个字节所以用long long 类型接收比较合适
long long L = reinterpret_cast<long long>(test);
cout << "L = " << L << endl;
//将数字转换成地址
void(*FuncName)() = reinterpret_cast<void(*)()>(L);
FuncName();
return 0;
}
原始转换就是将函数地址转换成相应的数字,又可以将数字转换成地址
常量转换
//常量转换可以做到去掉const,加上const,且只适用于(引用和指针)
//去掉const的写法
const int m = 3;
int& n = m; //这样写是错误的我们需要强制去掉const
int& q = const_cast<int&>(m); //这样就可以用引用了
//加上const的写法
int num = 5;
const int& val = const_cast<const int&>(num);
return 0;
动态转换
本篇博主暂不介绍强制转换
C++与C强制类型转换还是有小小的区别,C的强制类型转换: (类型)数据,C++的强制类型转换: 类型(数据)。//C写法
int m = (int)1.1;
//C++写法
int n = int(2.1);
cout << "m = " << m << endl << "n = " << n;
输出结果
m = 1
n = 2
认识lambda表达式
lambda的表达形式
lambda捕获列表:
- [ ]。空捕获列表,lambda不能使用所在函数中的变量。
- [=]。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
- [&]。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
- [this]。函数体内可以使用Lambda所在类中的成员变量。
- [a]。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
- [&a]。将a按引用进行传递。
- [=,&a, &b]。除a和b按引用进行传递外,其他参数都按值进行传递。
- [&, a, b]。除a和b按值进行传递外,其他参数都按引用进行传递。
- 值捕获
void func()
{
int i = 100;//局部变量
//将i拷贝到名为f的可调用对象
auto f = [i] { return i; };
i = 0;
int j = f(); //j=100,因为i是创建时拷贝的
- 引用捕获
void func()
{
int i = 100;//局部变量
//对象f包含i的引用
auto f = [&i] { return i; };
i = 0;
int j = f(); //j=0,传递的是引用
}
- 用值的方式捕获如何在函数体改变拷贝值
默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。假如我们希望能改变一个被捕获的变量的值,就必须在参数列表后面加上关键字mutable。而一个引用捕获的变量则不受此限制。
void func()
{
int i = 10, j = 10;
//加上mutable才可以在lambda函数中改变捕获的变量值
auto f = [i, &j]() mutable {
i = 100, j = 100;
};
i = 0, j = 0;
f();
//输出:0 100 因为i是值捕获,即使改变了函数体内部的i也不会影响外部i的变化,
//因此输出i是0,因为j是引用的方式捕获,函数体内部改变j,外部j也跟着变化
std::cout << i << " " << j << std::endl;
- 返回类型 ->
当我们需要为一个lambda定义返回类型时,需要使用尾置返回类型。返回类型若缺省,则根据函数体中的 return 语句进行推断(如果有多条return语句,需要保证类型一直,否则编译器无法自动推断)。默认情况下,如果一个lambda函数体不包含return语句,则编译器假定返回void。
void func()
{
auto f = []() ->double {
if (1 > 2)
return 1; //返回int
else
return 2.0; //返回double
};
std::cout << f() << std::endl;
}
如果不显示指定返回类型,则int和double两种返回类型会导致推断冲突。
C++结构体的改变
- 可以在结构体内部对变量赋初始值
#include<iostream>
using namespace std;
struct Info
{
int age = 18;
char name[50] = "张三";
int sex = 1;
};
int main()
{
Info info;
cout << "age = " << info.age << endl << "name = " << info.name << endl << "sex = " << info.sex;
return 0;
}
输出结果
age = 18
name = 张三
sex = 1
- 可以在结构体内部定义函数
#include<iostream>
using namespace std;
struct Info
{
int age = 18;
char name[50] = "张三";
int sex = 1;
//直接定义
void print()
{
printf("我是直接在结构体内部定义的函数\n");
}
//先声明
void print_1();
//打印结构体内部数据
void print_2()
{
cout << "age = " << age << endl << "name = " << name << endl << "sex = " << sex << endl;
}
//设置类函数
void set_Info(int a, const char* n, int s)
{
age = a;
strcpy(name, n);
int sex = s;
}
};
void Info::print_1()
{
printf("我是先声明后定义的函数\n");
}
int main()
{
Info info;
info.print();
info.print_1();
//没被更改时的数据
cout << "没被更改时的数据" << endl;
info.print_2();
cout << endl;
//被更改了的数据
cout << "被更改了的数据"<<endl;
info.set_Info(20, "李四", 1);
info.print_2();
return 0;
}
- 如何创建成员函数的函数指针,以及如何使用
#include<iostream>
using namespace std;
struct Info
{
int age = 18;
char name[50] = "张三";
int sex = 1;
//直接定义
void print()
{
printf("我是成员函数\n");
}
};
int main()
{
Info info;
//创建成员函数指针
void(Info::*func)() = &Info::print;
//使用
(info.*func)();
return 0;
}
输出结果
我是成员函数
string用法
在C++中提供了专门创建字符串的类型,但是string字符串与C语言中char*字符串又有所区别,string的字符串后面是没有 '\0' 的,所以没法用printf()函数打印如何创建string变量
#include<iostream>
#include<string>
using namespace std;
void Create_String()
{
//创建变量
//先定义再赋值
string s1;
s1 = "我是大帅比";
//定义并且赋值
string s2 = "我是大帅比";
//括号的方式赋值
string s3 = ("我是大帅比");
}
int main()
{
Create_String();
return 0;
}
字符串的比较
在C++中字符串比较是很方便的,直接利用运算符<,>,=,就可以实现比较#include<iostream>
#include<string>
using namespace std;
void CMP_String()
{
//原理和C语言中的strcmp函数实现原理一样
string s1 = "张三";
string s2 = "李四";
if (s1 > s2)
{
cout << "张三是李四大哥" << endl;
}
else
{
cout << "李四是张三大哥" << endl;
}
}
int main()
{
CMP_String();
return 0;
}
字符串的连接
用加号实现字符串的连接#include<iostream>
#include<string>
using namespace std;
void ADD_String()
{
//原理和C语言中的strcmp函数实现原理一样
string s1 = "张三";
string s2 = "和";
string s3 = "李四";
string s4 = "拼在了一起";
cout << s1 + s2 + s3 + s4 << endl;
string Add = s1 + s2 + s3 + s4;
cout << Add << endl;
}
int main()
{
ADD_String();
return 0;
}
//输出结果
张三和李四拼在了一起
张三和李四拼在了一起
字符串的下标遍历
#include<iostream>
#include<string>
using namespace std;
void traversal_String()
{
//I like you.
string s = "I like you.";
int size= s.size(); //统计字符串有效个数不包括'\0'
cout << "size = " << size << endl;
//第一种遍历方式
for (int i=0; i < s.size(); i++)
{
cout << s[i];
}
cout << endl;
//第二种遍历方式无需知道字符串长度
for (char c : s)
{
cout << c;
}
}
int main()
{
traversal_String();
return 0;
}
输出结果
size = 11
I like you.
I like you.
完