【C++核心编程】C++入门关键基础知识
我要成为C++领域大神 2024-07-27 13:05:03 阅读 94
🔥博客主页: 我要成为C++领域大神
🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️
本博客致力于知识分享,与更多的人进行学习交流
设计思想
C语言是面向过程,C++是面向对象。
C++是一门面向对象编程的语言,把问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为,更注重的是程序的整体设计,方便程序后期维护、优化和管理,让一个功能尽可能的通用。面向对象编程只有一个价值: 应对需求的变化,本意是要处理大型复杂系统的设计和实现。
常说的面向过程和面向对象,其本质还是在其设计思想上
面向过程
优点: 性能比面向对象高,比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展
面向对象
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
缺点: 因为类调用时需要实例化,开销比较大,比较消耗资源,性能比面向过程低。
C++是由C衍生出来的一门语言,不但兼容包含了C语言还增加了一些新特性函数重载,类、继承、多态,支持泛型编程(模板函数、模板类) ,强大的STL库等。面向对象的三大特性:封装、继承、多态。
C++的三大特性
1.封装:
将零散的数据和算法放到一个集合里,方便管理和使用。
2.复用性:
公共功能、过程的抽象,体现为能被重复使用的类、方法,就要求我们针对某一类功能而不是针对某一个功能去设计。
3.扩展性:
增加新的功能不影响原来已经封装好的功能。
输入输出语法上的小差异
C++使用iostream头文件
<code>#include<iostream> //1、标准输入输出流 不带.h
using namespace std; //2、打开一个标准命名空间 using:打开 namespace:命名空间 std:标准
输入输出格式不同
//C语言输出
printf("%d %c\n", a, aa);
//3、C++输出 cout:ostream类型的变量 <<:输出操作符 endl:本质上是一个函数
cout << a << " " << aa << endl;
//C语言输入
scanf_s("%d %c", &a, &aa);
cout << a << " " << aa << endl;
//4、C++输入 cin:istream类型的变量 >>:输入操作符
cin >> a >> aa;
cout << a << " " << aa << endl;
const char* p = "abc";
cout << p << endl;//输出字符串常量
cout << (void*)p << endl;//输出首元素地址
作用域
C++ 中的作用域(scope)指的是变量、函数或其他标识符的可见和可访问的范围。 变量作用域(Variable Scope)是指变量的生命周期和可见性,也就是变量在程序中的哪些部分可以使用。按照作用域(Scope)变量可分类为全局变量和局部变量。
还可按照生命周期(Lifetime)进行分类变量可分类为静态变量(Static Variables)和成员变量(Member Variables)。
在输入输出时,要指定作用域。如果未指定作用域,则默认最近的局部作用域。
:: 打开作用域运算符 格式:某一个作用域::变量名 可以是结构体、命名空间、类 若未进行指定,则默认为全局下的变量
cout << a << endl;//输出局部内的a
//:: 打开作用域 格式:某一个作用域::变量名 可以是结构体、命名空间、类 若未进行指定,则默认为全局下的变量
cout << ::a << endl;
cout << myspace::a << endl;
cout << myspace::aa << endl;
return 0;
动态申请空间
C++动态申请内存空间关键字:new,delete
对于计算机程序设计而言,变量和对象在内存中的分配都是编译器在编译程序时安排好的,这带来了极大的不便,如数组必须大开小用,指针必须指向一个已经存在的变量或对象。对于不能确定需要占用多少内存的情况,动态内存分配解决了这个问题。
new和delete运算符是用于动态分配和撤销内存的运算符。
一、new用法
1.开辟单变量地址空间
使用new运算符时必须已知数据类型,new运算符会向系统堆区申请足够的存储空间,如果申请成功,就返回该内存块的首地址,如果申请不成功,则返回零值。
new运算符返回的是一个指向所分配类型变量(对象)的指针。对所创建的变量或对象,都是通过该指针来间接操作的,而动态创建的对象本身没有标识符名。
一般使用格式:
格式1:指针变量名=new 类型标识符;
格式2:指针变量名=new 类型标识符(初始值);
格式3:指针变量名=new 类型标识符 [内存单元个数];
说明:格式1和格式2都是申请分配某一数据类型所占字节数的内存空间;但是格式2在内存分配成功后,同时将一初值存放到该内存单元中;而格式3可同时分配若干个内存单元,相当于形成一个动态数组。例如:
1)new int; //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址。int *a = new int 即为将一个int类型的地址赋值给整型指针a
2)int *a = new int(5) 作用同上,但是同时将整数空间赋值为5
2.开辟数组空间
对于数组进行动态分配的格式为:
指针变量名=new 类型名[下标表达式];
delete [ ] 指向该数组的指针变量名;
两式中的方括号是非常重要的,两者必须配对使用,如果delete语句中少了方括号,因编译器认为该指针是指向数组第一个元素的指针,会产生回收不彻底的问题(只回收了第一个元素所占空间),加了方括号后就转化为指向数组的指针,回收整个数组。
delete []
的方括号中不需要填数组元素数,系统自知。即使写了,编译器也忽略。
请注意“下标表达式”不必是常量表达式,即它的值不必在编译时确定,可以在运行时确定。
一维: int *a = new int[100]; //开辟一个大小为100的整型数组空间
二维: int (*a)[6] = new int[5][6]
三维及其以上:依此类推.
一般用法: new 类型 (初值)
二、delete用法
1. 删除单变量地址空间
int *a = new int;
delete a; //释放单个int的空间
2. 删除数组空间
int *a = new int[5];
delete []a; //释放int数组空间
malloc -free 与 new -delete的区别:
1、new -delete是C++中的关键字,需要编译器支持。malloc -free是函数,需要头文件的支持
2、new申请内存空间成功后返回的是对应类型的地址,不需要进行强转。malloc返回的是泛型指针(void*)一般情况下是需要进行强转的。
3、new申请空间的同时可以指定初始值,而malloc需要在申请成功后进行赋值
4、new申请结构体、类空间,自动调用构造函数,delete自动调用析构函数,malloc -free不会
//整型数组的 指针
int (**p6)[]=new (int(*)[]);
delete p6;
p6 = NULL;
//整型指针的 数组
int (**p7)= new (int* [5]);
delete[]p7;
C++中的布尔类型
#include<iostream>
#include<windows.h>
/*
C++支持bool类型,是关键字
占1个字节
*/
using namespace std;
int main() {
bool b = true;
b = false;
cout << b << endl;
cout << sizeof(bool) << endl;
cout << sizeof(BOOL) << endl;
return 0;
}
C语言中的BOOL类型要包含windows.h头文件,是通过typedef替换后的int类型,4个字节
C++中的bool类型是关键字,1个字节
C++中的string
C语言中的字符串定义,有三种方式:
const char* p = "123456";
char p2[4] = "123";//需要预留一个数组空间存放'\0'结束标记
char p3[] = "123";//会自动计算数组长度
对于第一种方式,我们不可以通过解引用修改字符串的内容,因为p指针指向的是字符串常量,存储在常量区,无法对其进行修改,但是我们可以修改指针的指向。
//p[1] = 'A'; 这种方式不可以修改,因为p指向的是常量区的内容
p = "ABC";
cout << p << endl;//可以修改p的指向,但是不能修改指向的内容;
对于第二种、第三种方式
char p2[4] = "123";
p2[1] = 'A';//1A3
//p2 = "ABC"; 这种方式不可取,因为p2是数组名,为常量,不可进行修改。
cout << p2 << endl;
C++中的字符串,可以用string关键字来定义:
string str = "ABCDEFG";
cout << str << endl; //ABCDEFG
str[1] = '2';
cout << str << endl; //A2CDEFG
string类型的字符串,可以直接用'='来进行赋值,而char类型字符串数组需要用strcpy函数来进行字符串复制
string str2 = str;
str2 = "sfddfsd";
strcpy_s(p2, "ABC");
cout << strcmp(p, p2) << endl;
对两个char类型字符数组,使用(==)和(!=)进行运算,得到的结果是数组首元素地址是否相等。而string类型字符串可以通过此方式来判断字符串是否相同。
str2 = "sfddfsd";
if (str2 == str) //string类型的字符串可以用== != 来验证是否相等
{
cout << "字符串相等" << endl;
}
else
{
cout << "字符串不等" << endl;
}
char类型字符串数组进行字符串拼接可以通过strcat函数进行拼接,而string类型字符串可以直接通过'+'进行连接
//字符串拼接 strcat();用来拼接char *类型字符串
str2 += str + p + p2 + "123";
cout << str2 << endl;//sfddfsdA2CDEFGABCABC123
C++字符串相关API
string类型字符串进行截取操作:
字符串名.substr(下标,截取长度)
//字符串截取 substr();
cout<<str2.substr(3/*截取下标*/, 4/*截取长度*/) << endl;//dfsd
长度计算:char类型字符串数组可以通过strlen函数计算字符串长度。string类型字符串可以通过:字符串名.length() 或 字符串名.size() 来计算长度
//长度计算
cout<<str2.length()<<" "<<str2.size() << endl;
//strlen(str2); 不能使用此方式计算长度
str2.c_str();//转换string类型字符串为char* 类型
cout << strlen(str2.c_str()) << endl;
string类型和常量字符串const char*类型间的相互转换,转换方式:string类型字符串名.c_str()
//p = str2;不能手动赋值,需要进行转换
p=str2.c_str();
cout << p << endl;
//str2.c_str() = p;
p = "123";
str2 = (string)p;
cout << str2<<endl;
增强for循环
通常我们遍历数组时,常用的写法for(;;),新标准允许了下面简化的写法:
for(type val:arr)
传统for循环可以通过循环次数控制结束位置,新标准在for中不能控制结束位置,只能从头到尾遍历。
函数参数默认值
函数参数默认值,即在函数声明处,给定函数形参一个默认值。在调用函数时,若未对函数传入参数,则使用形参的默认值。
void fun1(int a = 2) {
cout << "fun1 " << a << endl;
}
int main()
{
fun1();//fun1 2
fun1(5); //fun1 5
}
函数参数默认值不仅使用于单个参数,也适用于函数有多个参数。但是默认值指定的顺序必须是从左到右,并且中间不能有间断。
void fun2(int a, int b = 4) { //若函数有多个参数,则指定默认值应从左到右,并且不能间断
//错误样例:(int a,int b=4,int c)
cout << "fun2 " << a <<" "<< b << endl;
}
int main()
{
fun2(5); //fun2 5 4
fun2(5,6); //fun2 5 6
}
若只在声明处给定默认参数,结果正确
void fun3(int a=8);
int main()
{
//只在声明出给定默认值
fun3(); //ok
fun3(10);
}
void fun3(int a)
{
cout << "fun3 " << a << endl;
}
若只在定义处给定默认参数,不给函数传参,编译不会通过
void fun4(int a);
int main()
{
//只在定义处给定默认值
//fun4(); //error
fun4(20); //fun4 20
}
void fun4(int a = 10) {
cout << "fun4 " << a << endl;
}
若在定义和声明处同时给定默认参数,则会编译不通过: 重定义默认参数
void fun4(int a=8);
int main()
{
//声明定义处都给定默认值 编译报错
}
void fun4(int a = 10) {
cout << "fun4 " << a << endl;
}
大多数情况下,函数参数默认值在声明处指定
若是在全局下,指定多次,则会产生错误:重定义默认参数
但是可以通过局部作用域再次声明改变默认值,只在局部内生效
void fun3(int a = 5);
int main()
{
{ //只在局部作用域生效
void fun3(int a = 8);
fun3();
fun3(10);
}
{ //只在局部作用域生效
void fun3(int a = 9);
fun3();
fun3(15);
}
}
void fun3(int a)
{
cout << "fun3 " << a << endl;
}
函数重载
函数重载:在同一个作用域,函数名一样,参数列表不同(参数类型、数量不同)。
注意函数重载描述的是多个函数之间的关系。 函数的重载使得 C++ 程序员对完成类似功能的不同函数可以统一命名,减少了命名所花的心思。在调 用函数时,编译器会根据实参的类型、数量自动匹配对应类型函数。
#include<iostream>
using namespace std;
/*在同一个作用域下,函数名相同,
参数列表不同(类型,顺序、数量 不同)
对返回类型并没有任何要求 (可同可不同)
函数重载 是描述多个函数之间的关系*/
void fun(int a) {
cout << "fun(int) " << a << endl;
}
void fun(double a) {
cout << "fun(double) " << a << endl;
}
void fun(int a,double b) {
cout << "fun(int double) " << a<<" "<<b << endl;
}
void fun(double a, int b) {
cout << "fun(double int) " << a << " " << b << endl;
}
int main() {
fun(5);
fun(5.1);
fun(3, 5.1);
fun(10.8, 8);
}
函数重载的几种特殊情况:
//不可重载
// 1.重定义了,二者本质相同。
void fun2(int arr[]) {
cout << "fun2(int arr[]) " << endl;
}
void fun2(int* a) {
cout << "fun2(int* a) " << endl;
}
//这两种传参本质都是传入一个地址
C++ const对函数重载的影响
//2.不可以重载,本质相同。编译错误,提示重定义
void* fun2(int a) {
cout << "fun2(int a) " << endl;
}
void* fun2(const int a) {
cout << "fun2(const int a) " << endl;
}
//3.不是函数重载,char *a和char * const a,这两个都是指向字符串变量,不同的是char *a是指针变量 而char *const a是指针常量,这就和int i和const int i的关系一样了,所以也会提示重定义。
void fun(char *a)
{
cout << "non-const fun() " << a;
}
void fun(char * const a)
{
cout << "const fun() " << a;
}
int main()
{
char ptr[] = "hello world";
fun(ptr);
return 0;
}
//4.是函数重载,char *a 中a指向的是一个字符串变量,而const char *a指向的是一个字符串常量,所以当参数为字符串常量时,调用第二个函数,而当函数是字符串变量时,调用第一个函数。
void fun(char *a)
{
cout << "non-const fun() " << a;
}
void fun(const char *a)
{
cout << "const fun() " << a;
}
int main()
{
const char *ptr = "hello world";
fun(ptr);
return 0;
}
//4.可以重载,传入参数默认使用第二个函数,局部下再次声明可以用第一个
void fun4() {
cout << "fun4 " << endl;
}
void fun4(int a=10) {
cout << "fun4(int a=10) " << endl;
}
int main() {
fun4(20);
{
void fun4();
fun4();
}
}
nullptr
NULL与nullptr的区别
在C语言中,NULL是宏定义,本质为 ((void *)0),表示一个空指针常量
nullptr是C++中的关键字,空指针
NULL在作为函数参数时,默认匹配为整型0,在函数重载时容易混淆。
为什么要用nullptr?
为了解决整数0和空指针的混用问题
void fun(int a) {
cout << "fun(int a) "<< a << endl;
}
void fun(int* a) {
cout << "fun(int *a) ";
if (a) {
cout << *a << endl;
}
else {
cout << "指针为空" << endl;
}
}
int main() {
fun(NULL);//0
int* p = NULL;
fun(p);
fun((int*)NULL);
fun(nullptr);
return 0;
}
引用
引用的含义:对已存在的变量(一块空间)起别名
引用一个变量,& 在这里是定义引用的符合,而不是取地址
int a = 5; //含义:对已存在的变量(空间)起别名
int& b = a; // 引用一个变量,& 在这里是定义引用的符合,而不是取地址
cout << &a << " "<< & b << endl; //输出地址
b = 6;
cout << a <<" "<< b << endl; //6 6
对一块空间起别名,对别名操作就是在这块空间上的直接操作
可以通过0作为函数参数,来判断引用作为函数参数时是否属于函数重载
void fun(int a) { //只能改变形参的值
cout << "fun(int a) " << a << endl;
a = 10;
}
void fun(int& a) { //可以改变实参的值
//将参数传入函数相当于对参数起别名,是对参数的直接操作
cout << "fun(int &a) " << a << endl;
a = 20;
}
int main() {
fun(0);//0无法取别名,可以通过此方式判断参数为引用变量时,是否为函数重载
//fun(a);直接传入参数会有多个重载函数与参数类型匹配
void fun(int& a);//可以先声明引用函数,再使用。局部优先级高
fun(a);
}
函数传参的三种方式:值传递 引用传递 地址传递
若要对实参进行修改,可以使用 引用传递 和 地址传递
若只查看,则三种方式都可以, 但是更推荐 引用传递 和 地址传递 ,因为值传递产生的空间不可控
引用传递和地址传递的区别:
1. 引用传递不会申请额外的空间,地址传递会申请一个指针大小的空间
2. 引用不能为空,定义了就必须初始化。指针可以为空,并且定义之后无须初始化
3. 引用只能引用一个变量,指针的指向可以任意修改
4. 指针自增和引用自增意义不同
#include<iostream>
using namespace std;
void fun(int a) { //只能改变形参的值
cout << "fun(int a) " << a << endl;
a = 10;
}
void fun(int& a) { //可以改变实参的值
//将参数传入函数相当于对参数起别名,是对参数的直接操作
cout << "fun(int &a) " << a << endl;
a = 20;
}
void fun(int* a) {
cout << "fun(int *a) " << *a << endl;
}
int main() {
int a = 5;
int& b = a;
const int& c = 0;//常量引用 引用了常量
//-------------------------------------------------------------------------
int d = 2;
b = d;//这里不是重引用,仅仅是赋值
int* p = &a;
p = &d; //指针的指向可以任意修改
// int& q; 引用不能为空,定义了就必须初始化
int* p2;//指针可以为空,并且定义之后无须初始化
//引用传递不会申请额外的空间,地址传递会申请一个指针大小的空间
return 0;
}
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。