【C++核心编程】C++入门关键基础知识

🔥博客主页: 我要成为C++领域大神
🎥系列专栏【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️

本博客致力于知识分享,与更多的人进行学习交流

设计思想

C语言是面向过程,C++是面向对象。

C++是一门面向对象编程的语言,把问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为,更注重的是程序的整体设计,方便程序后期维护、优化和管理,让一个功能尽可能的通用。面向对象编程只有一个价值: 应对需求的变化,本意是要处理大型复杂系统的设计和实现。

常说的面向过程和面向对象,其本质还是在其设计思想

面向过程

优点: 性能比面向对象高,比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。

缺点:没有面向对象易维护、易复用、易扩展

面向对象

优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。

缺点: 因为类调用时需要实例化,开销比较大,比较消耗资源,性能比面向过程低。

C++是由C衍生出来的一门语言,不但兼容包含了C语言还增加了一些新特性函数重载,类、继承、多态,支持泛型编程(模板函数、模板类) ,强大的STL库等。面向对象的三大特性:封装、继承、多态。

C++的三大特性

1.封装:

将零散的数据和算法放到一个集合里,方便管理和使用。

2.复用性:

公共功能、过程的抽象,体现为能被重复使用的类、方法,就要求我们针对某一类功能而不是针对某一个功能去设计。

3.扩展性:

增加新的功能不影响原来已经封装好的功能。

输入输出语法上的小差异

C++使用iostream头文件

#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;
}
  • 57
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值