C++从入门到精通(非常详细)(上)

C++入门基础:

1.C++与C语言的区别(大厂面试高频题):

  • C++既可以面向对象又可以面向过程,持有面向对象三大特性:封装,继承,多态,而C语言只是面向过程,并没有面向对象的三大特性。
  • C++类型检查更为严格,C语言中类型转化几乎都是任意的,但是C++编译器对于类型转换进行非常严格检查,部分强制类型转化在C编译器中可以通过,但是C++编译器是无法通过的。
  • C++提供了泛型模版的支持(Template),同时也提供了模版标准库(STL)的支持,以及引用,运算符重载和命名空间(避免命名冲突)。
  • C++与C应用领域不同:C语言常用于直接控制硬件,特别是C语言在嵌入式领域应用很广,比如常见的驱动开发等与硬件直接打交道的领域,C++可以用于应用层开发,用户界面开发等与操作系统打交道的领域,特别是图形图像领域,几乎所有高性能图形图像库都是C++实现。

2.C++编译过程:预处理+编译+汇编+链接(动态链接与静态链接)(大厂面试高频题

  • 预处理:处理“#”之后以及特殊字符的指令。(在编译之前的工作)
  • 编译:将源文件(.cpp文件)翻译成机器语言(汇编),并生成.s文件。
  • 汇编:将汇编语言翻译成目标机器指令,并生成.o文件。
  • 链接:链接分为静态链接与动态链接。其实就是将所有的目标文件链接起来,并生成可执行文件即.exe文件。
  • 静态链接:可支持程序员我们自己开发程序模块,提高了效率。但是缺点就是浪费空间,消耗资源,而且更新迭代比较困难。
  • 动态链接:一般为.dll文件,动态链接是在运行时才将所有的目标文件链接起来(相当于推迟链接),一般情况下,我们通常将静态链接与动态链接结合使用。

3.C++中的头文件与源文件(.h文件与.cpp文件

  • 在C++的世界里,C++文件通常进行分层,包含头文件(.h文件)与源文件(.cpp文件)。
  • 头文件(.h文件):头文件是用来声明函数与变量,实现对函数的封装的。
  • 源文件(.cpp文件):源文件用来对声明函数的实现的。
  • < >与" "的区别(大厂面试高频题):引用< >,编译器是从系统库中寻找对应的头文件,一般用于标准库头文件;而引用" ",编译器首先是从当前文件夹寻找头文件,当找不到以后,再从系统文件中进行寻找,一般用于自己定义的头文件。

hello_world.h文件:

const char*message="hello world"; //字符串常量
void hello_world();

hello_world.cpp文件:

#include <iostream> //系统标准库
#include "hello_world.h"//自己定义的hello_world.h
using namespace std;//命名空间std
//对hello_world函数的实现
void hello_world(){
    std::cout<<message<<std::endl;
    //cout<<message<<endl;//同上
}
int main(int argc,char **argv){
    hello_world();
    std::cin.get();
    return 0;
}

运行结果:

hello world

通过上述的代码,我们其实可以将以上这些概括成:头是声明,源是定义,< >查找标准库," "用来寻找定义库。 

4.C++中的注释(通常常见的两种注释

  • // :一般通常用于行的注释(函数功能以及变量的含义等等)
  • /* */:一般用于块的注释

5.C++中的常用的输入输出(需包含头文件<iostream>

备注:ostream(输出流),istream(输入流),iostream(输入输出流),sstream(字符串流),ifstream(写入文件流),ofstream(写出文件流),fstream(文件流)

备注:流在中阶篇进行深入探讨

  • 输入:std::cin>>...>>...>>...(相当于C语言中scanf函数)
  • 输出:std::cout<<...<<...<<...(相当于C语言中的printf函数)
  • 换行:std::endl(相当于C语言中的\n)

6.C++的命名空间(using namespace std与自定义命名空间

  • std:C++标准库中的函数以及对象都是在std域中的。(为了防止命名冲突)例如我们自己创建对象时,创建名为cout的对象,若没有std命名空间,那么ostream中的对象cout就会与我们所创建的对象起冲突。
  • 为了能够顺利访问域中的东西,需在域名后加上“::”(相当于在某个地方拿东西,需要加上限定符,表示这东西是在这个地方拿的,并不是其他地方或者凭空出现的),例如想要访问std空间里面的东西,需写成:std::。
  • 自定义命名空间:当然在C++中我们也可以自定义命名空间,访问属于自己定义域中的内容。
#include <iostream>
namespace cherno{
    int age(25);
    char name[20]="cherno";
    float score=90.0;
}
using namespace cherno;//使用cherno的命名空间
int main(int argc,char **argv){
    std::cout<<age<<" "<<name<<" "<<score<<std::endl;
    return 0;
}

运行结果:

25 cherno 90

上述代码中,我们定义了一个名为cherno的命名空间,并使用了该命名空间里的内容,运行结果如我们所预想的一样,到这里一切都还顺利,不过这样使用命名空间真的会一直顺利下去吗?

#include <iostream>
namespace cherno{
    int age(25);
    char name[20]="cherno";
    float score=90.0;
}
using namespace cherno;
int main(int argc,char **argv){
    int age=80;
    std::cout<<age<<" "<<name<<" "<<score<<std::endl;
    return 0;
}

运行结果:

80 cherno 90

当我们定义一个与命名空间冲突的名字时,(与cherno的age同名),C++编译器在这里不认识cherno的age,会调用main函数里的age,(main函数为主入口,编译器都会从该入口进行调用,编译器首先遇见了main中的age,会默认之后的age都是这个)那么有什么办法可以避免这种情况呢?(统一用域)(另一种方法不介绍了)

#include <iostream>
namespace cherno{
    int age(25);
    char name[20]="cherno";
    float score=90.0;
}
using namespace cherno;//可加也可不加
int main(int argc,char **argv){
    int age=80;
    std::cout<<cherno::age<<" "<<cherno::name<<" "<<cherno::score<<std::endl;
    return 0;
}

运行结果:

25 cherno 90

这样写代码,可读性较高,而且也避免了命名的冲突,所以大家在写程序时,需要习惯写域。

备注:在命名空间里,也可写函数。

  • 嵌套式命名空间
#include <iostream>
namespace cherno{
    int age(25);
    char name[20]="cherno";
    float score=90.0;
    namespace lambda{
        auto add=[](auto a,auto b){
            return a+b;
        };
    }
}
// using namespace cherno;//可加也可不加
int main(int argc,char **argv){
    // int age=80;
    // std::cout<<cherno::age<<" "<<cherno::name<<" "<<cherno::score<<std::endl;
    int a=10,b=10;
    std::cout<<cherno::lambda::add(a,b)<<std::endl;
    return 0;
}

运行结果:

20

备注:上述代码使用了lambda函数(lambda在高阶进行深入探讨)

嵌套式其实就是域中域,哈哈哈,现在,相信聪明的大家已经能够掌握域与命名空间的知识啦! 

7.C++常用的数据类型

备注:括号中表示占用字节数

int:整形(4)short :短整形(2) long:长整型(4)long long:长长整形(8)

float:单精度(4)double:双精度(8)

bool:布尔类型(1)(true为1,flase为0)

char:字符型(1)(以ASCII值存储)

#include <iostream> 
#include <sstream>
int main(int argc,char **argv){
    std::stringstream stream;
    stream<<sizeof(int)<<" "<<sizeof(short)<<" "<<sizeof(long)<<" "<<sizeof(long long)<<std::endl;
    stream<<sizeof(float)<<" "<<sizeof(double)<<std::endl;
    stream<<true<<" "<<false<<" "<<sizeof(bool)<<std::endl;
    char ch=97;//'a'所对应的ASCII值97
    //char ch='a';(同上)
    stream<<ch<<" "<<sizeof(char)<<std::endl;
    std::cout<<stream.str();
    return 0;
}

运行结果:

4 2 4 8

4 8 

1 0 1

a 1

备注:C++中,字符型都可以用ASCII值来表示,上述代码使用了字符串流(sstream),sstream在中阶或者高阶篇进行深入探讨。

8.C++中的字符串型(需包含头文件<string>

  • C字符串通常使用char[maxn],而C++提供了字符串库(string)(提供一些字符串的常用算法,C++字符串也可作为迭代器进行使用)
​
#include <iostream>
#include <cstring>//c字符串库
#include <string> //c++字符串库
const int maxn=100;//c++风格常量
//#define maxn 100(同上)
int main(int argc,char **argv){
    char ch[maxn]="hello world!";//c风格字符串
    //c风格字符访问方式
    for(int i=0;i<strlen(ch);i++){
        std::cout<<ch[i];
    }
    std::cout<<std::endl;
    std::string strs="hello world!";
    //c++新访问方式
    for(char ch:strs){
        std::cout<<ch;
    }
    std::cout<<std::endl;
    // for(int i=0;i<strs.size();i++){
    //     std::cout<<strs[i];
    // }
    // std::endl;(同上)
    //c++迭代器访问
    for(auto iter=strs.begin();iter!=strs.end();iter++){
        std::cout<<*iter;
    }
    std::cout<<std::endl;
    //向末尾添加字符串
    std::string temp="welcome to c++ world!";
    strs.append(temp);
    //strs+=temp;(同上)
    std::cout<<strs<<std::endl;
    // strs.append("welcome to c++ world!");(同上)
    // strs.append(temp.begin(),temp.end());(同上)
    std::cout<<strs.at(1)<<std::endl;//等价于strs[1],结果是'e'
    std::cout<<strs.back()<<std::endl;//等价于strs[strs.size()-1],结果是'!'与之相反的是strs.front()
    // std::cout<<strs.c_str();//转化为c风格字符串
    // std::cout<<strs.compare("welcome to my world!");//比较字符串是否相等,相等返回1,不等返回0
    // std::cout<<strs.data()<<std::endl;//获取字符串
    auto iter=strs.erase(strs.begin(),strs.begin()+4);//删除前四个字符
    std::cout<<strs<<std::endl;
    //从0位置开始查找'o'的位置
    if(strs.find('o',0)!=std::string::npos){
        std::cout<<strs.find('o',0)<<std::endl;
    }
    //从1位置开始查找第一个'o'的位置,与之相反的是find_first_not_of
    if(strs.find_first_of('o',1)!=std::string::npos){
        std::cout<<strs.find_first_of('o',1)<<std::endl;
    }
    //在1位置插入'~'
    strs.insert(strs.begin()+1,'~');
    // char ch='~';(同上)
    // strs.insert(1,ch);
    std::cout<<strs<<std::endl;
    return 0;
}

​

运行结果:

hello world!
hello world!
hello world!
hello world!welcome to c++ world!
e
!
1
o world!welcome to c++ world!
0
3
o~ world!welcome to c++ world!

备注:C++字符串,灵活性以及相关对字符串的算法,大家可以每个都实现以下,加深理解! 

9.C++常用运算符(算术运算符,逻辑运算符,三目运算符

  • 算术运算符:>=(大于等于),<=(小于等于),!=(不等于)
  •                       >(大于),<(小于),==(等于)
  •                       +(加),-(减),*(乘),/(除)
  •                       %(余)
  • 逻辑运算符:&&(与)---> 两者为真才为真,两者有一假就为假
  •                       ||(或) ---> 两者只要有个真就是真,两者都为假,才是假
  •                       !(非)--->真成假,假成真
  • 三目运算符:条件?指令1:指令2  ---> 若条件为真,执行指令1,否则执行指令2

10.C++位运算(常用位运算

  • &(按位与):有0即0,全为1才为1

例如:0001&0001=0001,0010&0110=0010

  • |(按位或):有1即1,全为0才为0

例如:0011|0001=0011,1001|0110=1111

  • ^(按位异或):相同为0,不同为1

例如:0011^0001=0010,1001^0110=1111

  • ~(取反):0转为1,1转为0

例如:~0=1,~1=0

  • <<(左移):最高位舍去,最低位补0

例如:0001<<k(表示左移k位),假如k=2,结果是0100

  • >>(右移):最地位舍去,最高位补1

例如:1101>>k(表示右移k位),假如k=2,结果是0011 

位运算详解

备注:位运算执行快,效率高,位运算某些技巧以及常面题参考以上链接!!! 

11.C++函数(为什么要写函数

  • 为什么要写函数?
  • 不用函数计算三组两数相乘(代码如下):
#include <iostream> 
int main(int argc,char **argv){
    unsigned int a=2,b=3;//无符号整形,大于等于0
    std::cout<<a*b<<std::endl;
    int c=4,d=5;//有符号整形,可以有负数
    std::cout<<c*d<<std::endl;
    size_t e=6,f=8;//size_t ---> unsigned long long 
    std::cout<<e*f<<std::endl;
    return 0;
}

运行结果: 

6
20

48 

试想一下,假如若干的数据让你进行计算,那么如果与上述代码一样将一行一行数据计算,代码是不是非常糟糕呢? 那么我们该怎样做,才能将代码写的既简洁又高效呢?其实这边需要的就是函数了,我们将乘法这个功能,作为函数,进行封装,代码如下:

#include <iostream>
template <typename T> 
void mutiply(T &&a,T &&b){
    std::cout<<a*b<<std::endl;
}
int main(int argc,char **argv){
    mutiply(2,3);
    mutiply(4,5);
    mutiply(6,8);
    return 0;
}

运行结果: 

6
20
48

备注:利用了C++的函数模版+右值引用(之后高阶再继续深入探讨)

上面的代码,比我们之前的简洁多了,并且可读性高了一点呢!如果还有人有强迫症的话(本人就有很严重的强迫症),其实也可以对mutiply函数继续封装成一个独立的函数。代码如下:

#include <iostream>
template <typename T> 
void mutiply(T &&a,T &&b){
    std::cout<<a*b<<std::endl;
}
void test(){
    mutiply(2,3);
    mutiply(4,5);
    mutiply(6,8);
}
int main(int argc,char **argv){
    test();
    return 0;
}

运行结果:

6
20
48 

总结:写函数,其实就是想我们想做的事情封装,使代码简洁,易读。 

备注:函数指针在指针篇进行深入探讨

12.C++判断(if,if-else,switch

  • if:如果条件为真,执行相对应的命令

经典题:判断年份是否为闰年?(能被4整除,且不被100整数或者能被400整除的年份),代码如下:

#include <iostream> 
//判断是否是闰年
bool is_leap_year(unsigned int &year){
    bool res=false;
    if((year%4==0&&year%100!=0)||year%400==0)//如果满足闰年条件,更新res值
        res=true;
    return res;
}
int main(int argc,char **argv){
    unsigned int year;//无符号数,大于等于0
    std::cin>>year;//从键盘输入年份
    std::cout<<is_leap_year(year)<<std::endl;//为闰年输出1,不为闰年输出0
    return 0;
}

运行结果:(分别输入2023与2024)

0

1

  • if-else: 如果条件为真,执行相对应的命令1,否则执行对应的命令2

经典题:判断一个数是否是偶数,代码如下:

#include <iostream>
void is_even_number(unsigned int &number){
    if(number%2==0){
        std::cout<<"the number is even number"<<std::endl;
    }else{
        std::cout<<"the number is odd number"<<std::endl;
    }
} 
int main(int argc,char **argv){
    unsigned int number;
    std::cin>>number;
    is_even_number(number);
    return 0;
}

运行结果:(分别输入4,5)

the number is even number

the number is odd number 

  • switch:(选择语句)判断条件,如果哪条case符合,执行相对应的命令

 经典题:判断今天星期几?代码如下:

#include <iostream>
void which_day(unsigned int &number){
    switch(number){
    case 1:
        std::cout<<"the day is monday"<<std::endl;
        break;
    case 2:
        std::cout<<"the day is tuesday"<<std::endl;
        break;
    case 3:
        std::cout<<"the day is wednesday"<<std::endl;
        break;
    case 4:
        std::cout<<"the day is thursday"<<std::endl;
        break;
    case 5:
        std::cout<<"the day is friday"<<std::endl;
        break;
    case 6:
        std::cout<<"the day is saturday"<<std::endl;
    default:
        std::cout<<"the day is sunday"<<std::endl;
        break;
    }
} 
int main(int argc,char **argv){
    unsigned int number;
    std::cin>>number;
    which_day(number);
    return 0;
}

运行结果:(分别输入2,7)

the day is tuesday

the day is sunday 

注意:case下的执行命令后,需要加break,切记别漏 

备注:我们需要习惯写函数!!!

13.C++循环(for,while,do_while

  • for循环:(单层循环与多重循环)

单层for循环:

怎样使用for循环呢?或者说为什么要使用for循环呢?

经典题目:判断1~10中哪些是奇数,并分别打印出来

不使用for循环,使用枚举方法进行枚举,代码如下:

#include <iostream>
enum number{
    a=1,b,c,d,e,f,g,j,k,l
};
bool is_odd_number(int &&number){
    return number%2!=0?true:false;
}
void which_type_number(){
    if(is_odd_number(number::a))
        std::cout<<number::a<<" ";
    if(is_odd_number(number::b))
        std::cout<<number::b<<" ";
    if(is_odd_number(number::c))
        std::cout<<number::c<<" ";
    if(is_odd_number(number::d))
        std::cout<<number::d<<" ";
    if(is_odd_number(number::e))
        std::cout<<number::e<<" ";
    if(is_odd_number(number::f))
        std::cout<<number::f<<" ";
    if(is_odd_number(number::g))
        std::cout<<number::g<<" ";
    if(is_odd_number(number::j))
        std::cout<<number::j<<" ";
    if(is_odd_number(number::k))
        std::cout<<number::k<<" ";
    if(is_odd_number(number::l))
        std::cout<<number::l<<std::endl;
}
int main(int argc,char **argv){
    which_type_number();
    return 0;
}

运行结果:

1 3 5 7 9 

上述代码,通过枚举的方式进行处理1~10的所有奇数。如果说10个数没有压力,那么100个,1000个,10000个呢?oh,my god!怎么那么繁琐呢?那么我们该通过什么进行对代码的优化呢?那么其实这里就需要for循环了,优化后,代码如下:

#include <iostream>
void which_type_number(){
    for(unsigned int i=1;i<=10;i++){
        if(i%2!=0)
            std::cout<<i<<" ";
    }
    std::cout<<std::endl;
}
int main(int argc,char **argv){
    which_type_number();
    return 0;
}

运行结果:

1 3 5 7 9 

通过上述的两个代码,可以发现for循环可以简化代码,并循环重复执行同一个或者多种命令。

双重循环:

经典题:打印m行,n列带“*”矩阵,代码如下:

#include <iostream>
int m,n;//m:表示行 n:表示列
void print_matrix(){
    for(int i=0;i<m;i++){
        for(int j=0;j<n;j++){
            std::cout<<"*";//内层循环执行的命令
        }
        std::cout<<std::endl;//外层循环执行的命令
    }
}
int main(){
    std::cin>>m>>n;
    print_matrix();
    return 0;
}

运行结果:(输入3,4)

****
****
****  

  • while:与for有着异曲同工之妙

用while替换上述的经典题(打印奇数),代码如下:

#include <iostream>
void which_type_number(){
    int i=1;
    while(i<=10){
        if(i%2!=0)
            std::cout<<i<<" ";
        i++;
    }
    std::cout<<std::endl;
}
int main(int argc,char **argv){
    which_type_number();
    return 0;
}

运行结果:

1 3 5 7 9 

备注:使用while,先进行定义变量,再判断。

注意:使用while一定要对变量进行更新,不然会陷入死循环。 

  •  do-while:先执行命令,再进行判断(与while进行区分)

用do-while替换上述的经典题(打印奇数),代码如下:

#include <iostream>
void which_type_number(){
    int i=1;
    do{
        if(i%2!=0)
            std::cout<<i<<" ";
        i++;
    }while(i<=10);
    std::cout<<std::endl;
}
int main(int argc,char **argv){
    which_type_number();
    return 0;
}

运行结果:

1 3 5 7 9  

注意:使用do-while一定切记while后面必须加;,还需要注意变量的变化

14.C++主要控制流(continue,break,return

一般情况下,控制流与循环搭档

  • continue:跳过当前语句,继续进入下一次循环

经典题:水仙花数(求100~999的水仙花数)(各个位数的立方和等于自身),代码如下:

#include <iostream>
#include <math.h>
void print_number_of_daffodils(unsigned int start,unsigned int end){
    for(unsigned int i=start;i<end;i++){
        if(pow(i%10,3)+pow(i/10%10,3)+pow(i/10/10%10,3)!=i) continue;
        std::cout<<"i="<<i<<std::endl;
    }
}
int main(int argc,char **argv){
    print_number_of_daffodils(100,1000);
    return 0;
}

运行结果: 

i=153
i=370
i=371
i=407

上述代码,continue跳过当前语句,进入下一次循环 

  • break:退出当前循环体(退出最内层循环)

经典题:判断一个数是否是质数?(只能被本身整除的数)代码如下:

#include <iostream> 
#include <math.h>
//判断是否是质数
bool is_prime_number(int &number){
    bool res=true;
    for(int i=2;i<=sqrt(number);i++){
        if(number%i==0){
            res=false;
            break;
        }
    }
    return res;
}
int main(int argc,char **argv){
    int number;
    std::cin>>number;
    std::cout<<is_prime_number(number)<<std::endl;//是质数输出1,不是输出0
    return 0;
}

运行结果:(分别输入2,4,7,9)

1

0

1

0

 上述代码,break退出当前循环

  • return:退出循环体(退出最外层循环)

上述代码可用return替换,代码如下:

#include <iostream> 
#include <math.h>
//判断是否是质数
bool is_prime_number(int &number){
    for(int i=2;i<=sqrt(number);i++){
        if(number%i==0) return false;
    }
    return true;
}
int main(int argc,char **argv){
    int number;
    std::cin>>number;
    std::cout<<is_prime_number(number)<<std::endl;//是质数输出1,不是输出0
    return 0;
}

运行结果:(输入2,4,7,9)

1

0

1

上述代码,return表示退出最外层循环 

15.C++数组(普通数组+动态数组(std::vector)+std::array(STL提供的固定数组)

  • 普通数组:普通的数组(静态)

一维数组初始化:数据类型+数组名+[数组容量]={数据}或者数据类型+数组名+[ ]={数据}

备注:注意数组容量需选择合适的大小,防止越界

经典题:二分查找(在一维数组中,查找目标值(target),若找到,返回目标值的下标,若不能,返回-1),代码如下:

#include <iostream>
#include <algorithm>
int nums[]={1,5,4,7,0,10,8,11,13};
int length=sizeof(nums)/sizeof(int);//求解数组大小 sizeof(arr)/sizeof(int)
//二分查找
int serch_half_of_target(int start,int end,int &target){
//数组排序(从小到大排序)
//nums[]={0,1,4,5,7,8,10,11,13}
std::sort(nums,nums+length,[](const auto &u,const auto &v){
    return u<v;
});
//二分查找模版(也可使用递归)
while(start<=end){
    int mid=start+(end-start)/end;
    if(nums[mid]<target)
        start=mid+1;
    else if(nums[mid]>target)
        end=mid-1;
    else 
        return mid;
}
return -1;
}
//递归版本
// int serch_half_of_target(int start,int end,int &target){
// //数组排序(从小到大排序)
// //nums[]={0,1,4,5,7,8,10,11,13}
// std::sort(nums,nums+length,[](const auto &u,const auto &v){
//     return u<v;
// });
// //递归版本
// if(start>end)
//     return -1;
// int mid=start+(end-start)/end;
// if(nums[mid]==target)
//     return mid;
// else if(nums[mid]<target)
//     return serch_half_of_target(mid+1,end,target);
// else
//     return serch_half_of_target(start,mid-1,target);
// }

int main(int argc,char **argv){
    int target=5;
    std::cout<<serch_half_of_target(0,length-1,target)<<std::endl;
    return 0;
}

运行结果:

3

备注:数组指针与指针数组在指针篇进行深入探讨

使用sort函数进行排序,需包含<algorithm>头文件algorithm(c++编译器提供的算法库)) 

数组基本操作

访问一维数组:数组名+[下标](下标默认是从0开始),表示数组第(下标+1)个元素值

数组长度:利用sizeof,进行求解(sizeof(数组名)/sizeof(数据类型))

注意:二分查找模版较多,针对不同问题,需选择不同的版子,二分查找一般适用于数组元素基本有序的情况

备注:二分查找与递归在数据结构与算法篇进行深入探讨

二维数组初始化:数据类型+数组名[行的容量][列的容量]={{数据},{数据}...}            

经典题:旋转数组,代码如下:

#include <iostream>
const int len=3;
//#define len 3 (同上)
void rotate(int nums[len][len]){
    for(int i=0;i<len/2;i++){
        for(int j=0;j<len;j++){
            auto temp=nums[i][j];
            nums[i][j]=nums[len-i-1][j];
            nums[len-i-1][j]=temp;
        }
    }
    for(int i=0;i<len;i++){
        for(int j=0;j<i;j++){
            auto temp=nums[i][j];
            nums[i][j]=nums[j][i];
            nums[j][i]=temp;
        }
    }
}
void show(int nums[len][len]){
    for(int i=0;i<len;i++){
        for(int j=0;j<len;j++){
            std::cout<<nums[i][j]<<" ";
        }
        std::cout<<std::endl;
    }
}
int main(int argc,char **argv){
    int nums[len][len]={{1,2,3},{4,5,6},{7,8,9}};
    show(nums);//原先的矩阵
    std::cout<<std::endl;
    rotate(nums);
    show(nums);//旋转后的矩阵
    return 0;
}

运行结果:

1 2 3 
4 5 6 
7 8 9

7 4 1 
8 5 2 
9 6 3 

二维数组常用操作

访问数组:数组名+[行标][列标],表示第(行标+1)行,第(列标+1)列的元素值

强调:数组下标是从0开始

二维数组列数:使用sizeof(sizeof(arr[0])/sizeof(数组类型))

二维数组行数:使用sizeof(sizeof(arr)/sizeof(二维数组列数*sizeof(数据类型)))

备注:数组都是采用顺序存储

一维数组存储:

二维数组存储:

  • vector容器:标准模版库(STL)中的数组容器(动态),可作为迭代器使用(必须加<vector>头文件)

创建vector对象,代码如下:(vector提供了模版,vector<typename T>与vector<bool>)

#include <iostream>
#include <vector>
#include <string>
class student;
int main(int argc,char **argv){
    std::vector<int>vec1;
    std::vector<float>vec2;
    std::vector<std::string>vec3;
    std::vector<student*>vec4;
    ...
    return 0;
}

备注:vector基本上可以放常用的任何数据类型(包括类,指针等)

初始化一维vector对象:

  • 直接初始化,代码如下:
#include <iostream> 
#include <vector>
#include <ostream>
const int len=5;
std::ostream &operator<<(std::ostream &stream,std::vector<int>&ret){
        for(auto &elem:ret){
            stream<<elem<<" ";
        }
        return stream;
    }
int main(int argc,char **argv){
    //直接初始化
    std::vector<int>vec{1,2,3,4,5};
    // std::vector<int>vec={1,2,3,4,5};
    //利用第三方进行初始化
    std::vector<int>ret(vec);
    // std::vector<int>ret=vec;//vector类提供赋值重载函数operator=(同上)
    //利用指针初始化
    // std::vector<int>ret(vec.begin(),vec.end());//(同上)
    //利用resize函数+遍历,进行初始化
    // std::vector<int>ret;
    // ret.resize(vec.size());
    // //普通遍历
    // for(int i=0;i<vec.size();i++){
    //     ret[i]=vec[i];
    // }
    //利用assign函数进行初始化
    // std::vector<int>ret;
    // ret.assign(vec.begin(),vec.end());

    // int nums[len]={1,2,3,4,5};//普通数组(基本同上)
    //利用push_back进行初始化
    //push_back:向末尾添加元素(同ret={1,2,3,4,5})
    // std::vector<int>ret;
    // ret.push_back(1);
    // ret.push_back(2);
    // ret.push_back(3);
    // ret.push_back(4);
    // ret.push_back(5);
    //备注:与resize搭配利用遍历进行push_back更快
    std::cout<<ret<<std::endl;
    return 0;
}

运行结果:

1 2 3 4 5

备注:operator运算符重载在中阶篇进行深入探讨

  • vector常用功能(增删改查),代码如下:
#include <iostream> 
#include <vector>
#include <algorithm>
void show(std::vector<int>&ret){
    //获取数组的大小:size()与capacity()
    //普通遍历
    // for(int i=0;i<ret.size();i++){
    //     std::cout<<ret[i]<<" "; 
    // }
    //迭代器遍历
    for(auto iter=ret.begin();iter!=ret.end();iter++){
        std::cout<<*iter<<" ";
    }
    //另一种迭代器遍历
    // for(int &elem:ret){
    //     std::cout<<elem<<" ";
    // }
    std::cout<<std::endl;
}
int main(int argc,char **argv){
    std::vector<int>ret{1,2,3,4,5};
    show(ret);//第一次打印{1,2,3,4,5}
    //添加元素
    //末尾添加元素使用push_back()
    ret.push_back(6);//向vector末尾添加元素6
    show(ret);//第二次打印{1,2,3,4,5,6}
    //指定位置插入元素使用insert
    std::vector<int>::iterator iter=ret.begin();//插入的首位置(第一个元素)
    ret.insert(iter,7);//在第一个插入7 {7,1,2,3,4,5,6}
    ret.insert(iter+2,3,0);//在第三个位置插入3个0 {7,1,0,0,0,2,3,4,5,6}
    std::vector<int>new_ret{9,10,11,12,13,14};
    ret.insert(iter+3,new_ret.begin(),new_ret.end());//在第4个位置插入new_ret数组的所有元素 {7,1,0,9,10,11,12,13,14,0,0,2,3,4,5,6}
    show(ret);//第三次打印
    //删除元素
    //末尾删除元素
    ret.pop_back();//删除ret数组最后一个元素 {7,1,0,9,10,11,12,13,14,0,0,2,3,4,5}
    show(ret);//第四次打印
    //删除指定位置的元素
    ret.erase(ret.begin());//删除第一个元素 {1,0,9,10,11,12,13,14,0,0,2,3,4,5}
    ret.erase(ret.begin()+2);//删除第三个元素 {1,0,10,11,12,13,14,0,0,2,3,4,5}
    ret.erase(ret.begin()+1,ret.begin()+4);//删除第2个~第4个元素 {1,12,13,14,0,0,2,3,4,5}
    //删除指定元素
    // ret.erase(std::remove(ret.begin(),ret.end(),5),ret.end());//删除指定为5的元素
    //通过迭代器删除指定元素
    // for(auto iter=ret.begin();iter!=ret.end();){
    //     if(*iter==5)
    //         ret.erase(iter);
    //     iter++;
    // }
    show(ret);//第五次打印
    //修改元素值
    ret[2]=100;//第三个值改为100
    show(ret);//第六次打印 {1,12,100,14,0,0,2,3,4,5}
    //查找某个元素
    //如果查找到元素返回下标
    if(std::find(ret.begin(),ret.end(),100)!=ret.end()){
        std::cout<<std::distance(ret.begin(),std::find(ret.begin(),ret.end(),100))<<std::endl; // 2
    }
    //遍历方法进行查找
    //普通遍历
    // for(int i=0;i<ret.size();i++){
    //     if(ret[i]==100)
    //         std::cout<<i<<std::endl;
    // }
    //迭代器遍历(指针)
    // for(auto iter=ret.begin();iter!=ret.end();iter++){
    //     if(*iter==100)
    //         std::cout<<iter-ret.begin()<<std::endl;
    // }
    return 0;
}

运行结果:

1 2 3 4 5
1 2 3 4 5 6
7 1 0 9 10 11 12 13 14 0 0 2 3 4 5 6
7 1 0 9 10 11 12 13 14 0 0 2 3 4 5
1 12 13 14 0 0 2 3 4 5
1 12 100 14 0 0 2 3 4 5

备注:auto:自动推导类型,在中阶篇进行深入探讨 

二维数组:基本同一维(略) 

补充(<size_t Count,const std::allocator<int>&Val>)+resize用法:

#include <iostream>
#include <vector>
const int n=10;
std::ostream &operator<<(std::ostream &stream,std::vector<int>&ret){
    for(auto elem:ret){
       stream<<elem<<" ";
    }
    stream<<std::endl;
    return stream;
}
int main(){
    std::vector<int>ret(n,8);//定义10个8
    std::cout<<ret;//第一次打印 {8,8,8,8,8,8,8,8}
    ret.resize(3);//设置数组大小为3
    std::cout<<ret;//第二次打印 {8,8,8}
    ret.resize(7);//扩充数组大小为7
    std::cout<<ret;//第三次打印 {8,8,8,0,0,0,0}
}

运行结果:

8 8 8 8 8 8 8 8 8 8 
8 8 8
8 8 8 0 0 0 0 

 备注:STL提供的vector容器用法很多,需要大家多多练习,孰能生巧!!!

  • std::array:STL提供的固定数组(必须加<array>头文件)

固定数组(std::array)模版:std::array<typename T,size_t Size>

自己的(std::array)Demo,代码如下:

#include <array>
#include <iostream> 
#include <sstream> 
std::array<int,5>array_a={1,3,5};//创建名为array_a的数组,数据为{1,3,5,0,0}
void show(){
    //普通数组访问方式
    // for(int i=0;i<array_a.size();i++){
    //     std::cout<<array_a[i]<<" ";
    // }
    //C++数组访问方式
    // for(auto &elem:array_a)
    //     std::cout<<elem<<" ";
    //迭代器访问数组
    for(auto iter=array_a.begin();iter!=array_a.end();iter++){
        std::cout<<*iter<<" ";
    }
    std::cout<<std::endl;
}
int main(int argc,char **argv){
    std::stringstream stream;
    /*
    array_a.front():访问数组第一个元素
    array_a.back():访问数组最后一个元素
    array_a.at(2):访问数组第3个元素
    array_a.data():返回数组的地址
    array_a.size():数组的长度
    array_a.empty():数组是否为空
    */
    show();//第一次显示
    stream<<array_a.front()<<" "<<array_a.back()<<" "<<array_a.at(2)<<" "<<array_a.data()
    <<" "<<array_a.size()<<" "<<array_a.empty()<<std::endl;
    std::cout<<stream.str();
    
    //将数组所有的数据填充为4
    array_a.fill(4);
    show();//第二次显示

    return 0;
}

运行结果:

1 3 5 0 0 
1 0 5 0x7ff63c703000 5 0
4 4 4 4 4

备注:STL提供的std::array(固定数组),需要多多练习,熟能生巧!!! 

16.C++枚举(enum

  • enum:一些值的集合,如果想要给枚举更实用的定义,那就是给这些值指定一个名称

经典题:写日志(不同等级会发出不同的日志信息),代码如下:

#include <iostream>
enum level:int{
    levelError=0,levelWarning,levelInfo
};
int WriteLog(int &&level,const char *message){
    if(level>=levelInfo){
        std::cout<<"[INFO]"<<message<<std::endl;
        return 0;
    }
    if(level>=levelWarning){
        std::cout<<"[WARNING]"<<message<<std::endl;
        return 0;
    }
    if(level>=levelError){
        std::cout<<"[ERROR]"<<message<<std::endl;
        return 0;
    }
    return 0;
}
void test(){
    WriteLog(0,"Welcome to C++ World");
    WriteLog(1,"Welcome to C++ World");
    WriteLog(2,"Welcome to C++ World");
}
int main(int argc,char **argv){
    test();
    return 0;
}

运行结果:

[ERROR]Welcome to C++ World
[WARNING]Welcome to C++ World
[INFO]Welcome to C++ World

备注:默认情况下,enum里的值都是从0开始的依次往后加1,当然程序员可以自己定义enum里面元素值是多少

  • 构造方法:enum+名称{元素};或者enum+名称++数据类型{元素};
  • 使用方法:名称+::+元素名(使用域进行访问)

备注:使用时,使用域名访问是最好的,如果不使用域名,需要看enum在不在作用域里面

总结:使用enum(枚举体),主要为了保证代码的可读性

17.C++结构体与联合体(struct与union

  • struct(结构体):是一种数据结构,结构体可以声明成变量,指针以及数组,用来实现更为复杂的数据结构,当然结构体还包括成员变量以及成员函数等等。(其实就是将一套数据封装成一个结构体)

经典结构体:完成对某人的信息进行封装(包括姓名,年龄,电话以及地址等等),代码如下:

#include <iostream>
#include <string> 
const int maxn=50;
struct person{
    unsigned int age;
    char name[maxn];
    char tel_phone[maxn];
    std::string address;
    void eat(const  char*message){
        std::cout<<name<<" love eat "<<message<<std::endl; 
    }
    void drink(const char *message){
        std::cout<<name<<" love drink "<<message<<std::endl;
    }
}cherno;
std::ostream &operator<<(std::ostream &stream,person &cherno){
    stream<<cherno.age<<" "<<cherno.name<<" "<<cherno.tel_phone<<" "<<cherno.address<<std::endl;
    return stream;
}
int main(int argc,char **argv){
    cherno={20,"cherno","88888888","New York"};
    std::cout<<cherno;
    cherno.eat("apple");
    cherno.drink("coco");
    return 0;
}

运行结果:

20 cherno 88888888 New York
cherno love eat apple
cherno love drink coco 

补充:

结构体声明,参考代码如下:

#include <iostream> 
const int maxn=50;
/*
声明方法
struct+结构体名+{成员};
*/

/*声明一个名为student的结构体
成员分别是:age:年龄
name:姓名
stu_id:学号
score:总分
*/
struct student{
    unsigned int age;//年龄
    char name[maxn];//姓名
    unsigned int stu_id; //学号
    float score;//总分
};

/*可在构体后加上结构体定义,如下所示
student_1:学生1
student_2:学生2
备注:并未对学生1与学生2进行初始化
*/
// struct student{
//     unsigned int age;//年龄
//     char name[maxn];//姓名
//     unsigned int stu_id; //学号
//     float score;//总分
// }student_1,student_2;

/*
对上述的学生1与学生2初始化
学生1信息初始化为:年龄18,姓名cherno,学号1,总分100
学生2信息初始化为:年龄19,姓名thread,学号2,总分99
*/
// struct student{
//     unsigned int age;//年龄
//     char name[maxn];//姓名
//     unsigned int stu_id; //学号
//     float score;//总分
// }student_1{18,"cherno",1,100.0},student_2{19,"thread",2,99.0};

int main(int argc,char **argv){
    return 0;
}

备注:上述代码将一部分结构体定义给出,需要大家手动练习练习,熟能生巧!!!

结构体定义(上述代码补充),参考代码如下:

#include <iostream> 
const int maxn=50;
/*
声明方法
struct+结构体名+{成员};
*/

/*声明一个名为student的结构体
成员分别是:age:年龄
name:姓名
stu_id:学号
score:总分
*/
struct student{
    unsigned int age;//年龄
    char name[maxn];//姓名
    unsigned int stu_id; //学号
    float score;//总分
};
int main(int argc,char **argv){
    /*
    普通定义 
    student_1:学生1
    student_2:学生2
    */
    student student_1={18,"cherno",1,100.0};
    student student_2={19,"thread",2,99.0};

    /*
    指针定义 
    *student_1:学生1
    *student_2:学生2
    */
    // student *student_1=&student_1;
    // student *student_2=&student_2;
    /*
    数组定义
    students[];
    */
//    student students[10];
//    students[1]={18,"cherno",1,100.0};
//    students[2]={19,"thread",2,99.0};
    return 0;
}

纠正:上述代码定义结构体数组时,下标错误,正确代码请看下文代码 

结构体访问(上述代码补充),代码如下:

#include <iostream> 
#include <sstream>
const int maxn=50;
/*
声明方法
struct+结构体名+{成员};
*/
 
/*声明一个名为student的结构体
成员分别是:age:年龄
name:姓名
stu_id:学号
score:总分
*/
struct student{
    unsigned int age;//年龄
    char name[maxn];//姓名
    unsigned int stu_id; //学号
    float score;//总分
};
int main(int argc,char **argv){
    /*
    普通定义 
    student_1:学生1
    student_2:学生2
    */
    student student_1={18,"cherno",1,100.0};
    student student_2={19,"thread",2,99.0};
    //普通访问
    std::stringstream stream;
    stream<<student_1.age<<" "<<student_1.name<<" "<<student_1.stu_id<<" "<<student_1.score<<std::endl;
    stream<<student_2.age<<" "<<student_2.name<<" "<<student_2.stu_id<<" "<<student_2.score<<std::endl;
    std::cout<<stream.str();
    /*
    指针定义 
    *student_1:学生1
    *student_2:学生2
    */
    
    // student *student_1=&student_1;
    // student *student_2=&student_2;
    //指针访问
    // std::stringstream stream;
    // stream<<student_1->age<<" "<<student_1->name<<" "<<student_1->stu_id<<" "<<student_1->score<<std::endl;
    /*
    数组定义
    students[];
    */
    //数组访问
    // student students[10];
    // students[0]={18,"cherno",1,100.0};
    // students[1]={19,"thread",2,99.0};
    // std::stringstream stream;
    // stream<<students[0].age<<" "<<students[0].name<<" "<<students[0].stu_id<<" "<<students[0].score<<std::endl;
    return 0;
}

运行结果:

18 cherno 1 100
19 thread 2 99

备注:结构体需要大家多多练习,孰能生巧!!! 

struct结构体内存(遵循内存对齐原则):

  • 内存占用:每个成员变量都有自己的一片内存,并且互不干涉,遵循内存对齐原则,结构体占用内存长度是所有的成员变量长度之和。

注意struct结构体占用内存仅仅是成员变量所占用的内存,而成员函数并不占用内存

备注:内存对齐原则需要大家进行理解!!! 

  • union(联合体): 
  • 内存占用:共用同一片内存,并且只有一个成员变量能得到这块内存的使用权,联合体总长度至少满足最大成员变量占用内存大小,并且联合体占用内存长度还需要满足是所有成员变量长度的整数倍

union(联合体):联合体详解

18.C++指针(普通指针+数组指针+指针数组+函数指针+多级指针

  • 指针与引用的区别(大厂面试高频题):

1.指针与引用都是地址的概念,指针是指向某一块内存,它的内容存储的是指向内存的地址,引用是某块内存的别名程序为指针分配内存,而不为引用分配

2.指针使用前需要加*号,而引用需要加&号

3.引用在定义时被初始化,之后不能被修改,不过指针能够被修改。即引用的对象不能改指针指向的对象是可以修改的

4.指针被使用前需要判断是否为空,而引用不需要。

5.引用只有一级,而指针是有多级指针的

6.运算符++与--有区别,指针是指向下一个或者上一个对象,而引用就是对象本身内容进行运算

  • 普通指针:指向某一块内存,它的内容存储的是指向内存的地址。

普通指针常用操作,参考代码如下:

#include <iostream> 
int main(int argc,char **argv){
    int aa=8;
    int *ptr=&aa;
    std::cout<<"ptr="<<ptr<<std::endl;
    //取地址加&号
    std::cout<<"aa's address="<<&aa<<std::endl;
    //取地址里面的内容需要解指针,加*号
    std::cout<<"ptr's content="<<*ptr<<std::endl;
    /*
    引用其实就是给变量起个另外的名字,你还是你自己
    */
    int &bb=aa;
    std::cout<<"bb's address"<<&bb<<std::endl;
    std::cout<<"aa's address="<<&aa<<std::endl;
    std::cout<<"bb's content="<<bb<<std::endl;
    return 0;
}

运行结果:

ptr=0x5ec79ff81c
aa's address=0x5ec79ff81c
ptr's content=8
bb's address0x5ec79ff81c
aa's address=0x5ec79ff81c
bb's content=8 

备注:引用可以代替指针进行使用

经典题:两数交换 

错误做法,参考代码如下:

#include <iostream> 
void swap(int a,int b){
    int temp=a;
    a=b;
    b=temp;
}
int main(int argc,char **argv){
    int a=2,b=3;
    swap(a,b);
    std::cout<<"a="<<a<<" "<<"b="<<b<<std::endl;
    return 0;
}

运行结果:

a=2 b=3 

修改代码,查看a,b地址,代码如下:

#include <iostream> 
void swap(int a,int b){
    std::cout<<&a<<std::endl;
    std::cout<<&b<<std::endl;
    int temp=a;
    a=b;
    b=temp;
    std::cout<<&a<<std::endl;
    std::cout<<&b<<std::endl;
}
int main(int argc,char **argv){
    int a=2,b=3;
    swap(a,b);
    std::cout<<"a="<<a<<" "<<"b="<<b<<std::endl;
    return 0;
}

运行结果:

0x1229bff8a0
0x1229bff8a8
0x1229bff8a0
0x1229bff8a8
a=2 b=3

备注:传统的值传递并没有达到,实参交换的效果

怎样解决这种麻烦呢,这边就提供了两种方式:指针传递以及引用传递

指针传递,参考代码如下:

#include <iostream> 
void swap(int *a,int *b){
    std::cout<<a<<std::endl;
    std::cout<<b<<std::endl;
    int *temp=a;
    a=b;
    b=temp;
    std::cout<<a<<std::endl;
    std::cout<<b<<std::endl;
}
int main(int argc,char **argv){
    int a=2,b=3;
    swap(&a,&b);
    std::cout<<"a="<<a<<" "<<"b="<<b<<std::endl;
    return 0;
}

运行结果:

0x40e35ffdac
0x40e35ffda8
0x40e35ffda8
0x40e35ffdac
a=2 b=3  

备注:指针传递,达到了实参交换的效果

引用传递,参考代码如下:

#include <iostream> 
void swap(int &a,int &b){
    int temp=a;
    a=b;
    b=temp;
}
int main(int argc,char **argv){
    int a=2,b=3;
    swap(a,b);
    std::cout<<"a="<<a<<" "<<"b="<<b<<std::endl;
    return 0;
}

运行结果:

a=3 b=2  

备注:引用传递也完美完成了实参交换

注意:形参是函数体用来接收传递值的,而实参用于真正传递给函数的值

总结:作者通过<time>,已验证指针传递与引用传递同样快,大家需要习惯使用引用!!! 

  • 数组指针:

数组指针一般形式:数据类型+(*指针名)[常量表达式]

一维数组指针,参考代码如下:

#include <iostream>
const unsigned int len=5;
int main(int argc,char **argv){
    int aa[len]={20,12,1,3,22};
    int (*ptr)[len];
    ptr=&aa;
    //遍历,打印数组
    for(unsigned int i=0;i<len;i++){
        std::cout<<(*ptr)[i]<<" ";
    }
    std::cout<<std::endl;
    return 0;
}

运行结果:

20 12 1 3 22 

备注:数组指针是指针指向数组

具体一维数组指针结构图:

二维数组指针,参考代码如下:

#include <iostream>
const unsigned int row=3;
const unsigned int cow=4;
int main(int argc,char **argv){
    int aa[row][cow]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    int (*ptr)[cow];
    ptr=aa;
    for(unsigned int i=0;i<row;i++){
        for(unsigned int j=0;j<cow;j++){
            std::cout<<*(*(ptr+i)+j)<<" ";
        }
        std::cout<<std::endl;
    }
    return 0;
}

运行结果:

1 2 3 4 
5 6 7 8
9 10 11 12 

具体二维数组指针结构图:

备注:p++是指向的是数组第二行的首地址 

  • 指针数组:

指针数组一般形式:数据类型+*+指针名[常量表达式]或者数据类型+*+数组名+[ ]

存储形式:以指针形式存储(<typename T>)(T *[ ])

备注:T类型数组存储是指针(模版在高阶篇进行深入探讨)

一维指针数组,参考代码如下:

​
#include <iostream>
#include <algorithm> 
template <typename T>
void one_dimensional_ptr_array(T res,int &&length){
    /*
    一维指针数组常用操作:
    遍历
    指针数组运算
    */
    T ptr=res;
    for(int i=0;i<length;i++){
        std::cout<<*(ptr+i)<<" ";
    }
    std::cout<<std::endl;
    //(++,--) 备注:--与++一致
    (*ptr)++;
    for(int i=0;i<length;i++){
        std::cout<<*(ptr+i)<<" ";
    }
    std::cout<<std::endl;
    //遍历
    while(length){
        std::cout<<*(ptr++)<<" ";
        length--;
    }
    std::cout<<std::endl;
    /*
    常用算法
    排序,旋转
    */
    //排序(从小到大)
    std::sort(res,res+length,[](const auto &u,const auto &v){
        return u<v;});
    // std::sort(res,res+length,[](const auto &u,const auto &v){
    //     return u>v;});//从大到小排序
    std::reverse(res,res+length);//旋转  
}
int main(int argc,char **argv){
    int res[]={2,12,1,9,20};
    one_dimensional_ptr_array(res,sizeof(res)/sizeof(int));
    char *strs[]={"chinese","cherno","20","love c++"};
    for(int i=0;i<sizeof(strs)/sizeof(strs[0]);i++){
        std::cout<<*(strs+i)<<" ";
    }
    return 0;
}

​

运行结果:

2 12 1 9 20 
3 12 1 9 20
3 12 1 9 20
chinese cherno 20 love c++

备注:指针数组名就是指针,代表着数组的首地址

具体一维指针数组结构图:(res是一维数组)

二维指针数组,参考代码如下: 

#include <iostream>
const unsigned int row=3;
const unsigned int cow=4;
int main(int argc,char **argv){
    int aa[row][cow]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    int *ptr[3];
    for(unsigned int i=0;i<row;i++){
        ptr[i]=aa[i];
    }
    //遍历,打印二维数组
    for(unsigned int i=0;i<row;i++){
        for(unsigned int j=0;j<cow;j++){
            std::cout<<*(ptr[i]+j)<<" ";
            /*
            std::cout<<ptr[i][j]<<" ";
            std::cout<<*(*(ptr+i)+j)<<" ";
            std::cout<<(*(ptr+i))[j]<<" ";(同上)
            */
        }
        std::cout<<std::endl;
    }
    return 0;
}

运行结果:

1 2 3 4
5 6 7 8
9 10 11 12 

具体二维指针数组结构图:

备注:指针数组就是存储指针的数组(指针存储存储对象的地址) 

  • 函数指针:

一般形式:数据类型+(*指针名(函数名))+(数据类型+数据名,数据类型+数据名,......)

                  数据类型+(*指针名(函数名))+(数据类型,数据类型,......)

备注:省略*也行

参考代码如下:

#include <iostream> 
float add(float x,float y){
    return x+y;
}
float mutiply(int &value,float &x,float &y, float (*ptr)(float ,float)){
    return value*ptr(x,y);
}
int main(int argc,char **argv){
    int value=10;
    float x=1.1,y=2.2;
    // auto function=[](float x,float y)->float{
    //     return x+y;(同下)
    // };
    // float res=mutiply(value,x,y,[](float x,float y)->float{
    //     return x+y;
    // });(同下)
    float res=mutiply(value,x,y,add);
    std::cout<<res<<std::endl;
    return 0;
}

运行结果:

33

备注:函数指针实质是指向函数的指针,搭配lambda逼格会提升一个档次哦!!!

具体函数指针结构图:

  • 指针函数:

一般形式:数据类型+*+指针名(函数名)+(数据类型+数据名,数据类型+数据名,......)

                  数据类型+*+指针名(函数名)+(数据类型,数据类型,......)

备注:一般通过函数返回指针

参考代码如下:

#include <iostream>
float (*mutiply(const char *ch,float(*p)(float,float)))(float x,float y){
    if(ch=="mutiply")
        return p;
    return nullptr;
}
int main(int argc,char **argv){
    auto func=mutiply("mutiply",[](float x,float y)->float{
        return x*y;
    });
    std::cout<<func(1.1,10.0)<<std::endl;
    return 0;
}

运行结果:

11 

指针函数具体结构图:

备注:指针函数实质就是返回指向函数的指针 

  • 多级指针:

二级指针:指向指针的指针,存储的是指针的地址

一般形式:加两个*

参考代码如下:

#include <iostream> 
#include <sstream> 
int main(int argc,char **argv){
    unsigned int value=8;
    unsigned int *p=&value;
    unsigned int **ptr=&p;
    unsigned int ***pptr=&ptr;
    std::stringstream stream;
    stream<<"value's address:"<<&value<<" "<<"value's value:"<<value<<std::endl;
    stream<<"p="<<p<<" "<<"p's address:"<<&p<<" "<<"p's content:"<<*p<<std::endl;
    stream<<"ptr="<<ptr<<" "<<"ptr's address:"<<&ptr<<" "<<"ptr's content:"<<*ptr<<std::endl;
    stream<<"pptr="<<pptr<<" "<<"pptr's address:"<<&pptr<<" "<<"pptr's content:"<<*pptr<<std::endl;
    std::cout<<stream.str();
    return 0;
}

运行结果:

value's address:0x9e085ff86c value's value:8
p=0x9e085ff86c p's address:0x9e085ff860 p's content:8
ptr=0x9e085ff860 ptr's address:0x9e085ff858 ptr's content:0x9e085ff86c
pptr=0x9e085ff858 pptr's address:0x9e085ff850 pptr's content:0x9e085ff860 

备注:通过上述代码,得出结论,二级指针存储的是一级指针的地址指向一级指针,三级指针存储的是二级指针的地址指向二级指针,依次类推。

多级指针具体结构图:

补充:指针作为形参应用(一级指针与二级指针)+(指针引用混用)

一级指针应用:

经典题:比较三数大小,输出最大的数,代码如下:

#include <iostream>
void cout_max_value(float *x,float *y,float *z){
    float *max_ptr=x;
    if(*max_ptr<*y)
        max_ptr=y;
    if(*max_ptr<*z)
        max_ptr=z;
    std::cout<<*max_ptr<<std::endl;
}
int main(int argc,char **argv){
    float x=10,y=4,z=18;
    cout_max_value(&x,&y,&z);
    return 0;
}

运行结果:

18 

备注:一级指针作为形参,将实参地址传入即可

二级指针应用:

经典题:比较两个字符串是否相等,若为真返回1,否则返回0,代码如下:

#include <iostream>
const int len=12; 
bool compare_strs(char **s1,char **s2){
    for(unsigned int i=0;i<len;i++){
        if(*(*s1+i)==*(*s2+i)) continue;
        return false;
    }
    return true;
}
int main(int argc,char **argv){
    char s1[len]="hello world";
    char s2[len]="hello werld";
    char ss1[len]="hello world";
    char *p1=s1;
    char *p2=s2;
    char *p3=ss1;
    std::cout<<compare_strs(&p1,&p2)<<std::endl;
    std::cout<<compare_strs(&p1,&p3)<<std::endl;
    return 0;
}

运行结果:

0

备注:二级指针作为形参,将指向实参的指针地址传入即可

引用与指针混用作为形参:

一般形式:数据类型+*+&+指针名(形参名)

上述代码修改为,如下:

#include <iostream>
const int len=12; 
bool compare_strs(char *&s1,char *&s2){
    for(unsigned int i=0;i<len;i++){
        if(*(s1+i)==*(s2+i)) continue;
        return false;
    }
    return true;
}
int main(int argc,char **argv){
    char s1[len]="hello world";
    char s2[len]="hello werld";
    char ss1[len]="hello world";
    char *p1=s1;
    char *p2=s2;
    char *p3=ss1;
    std::cout<<compare_strs(p1,p2)<<std::endl;
    std::cout<<compare_strs(p1,p3)<<std::endl;
    return 0;
}

运行结果:

0

备注:指针与引用混合作为形参,将指向实参的指针传入即可

本质:给指针起别名,传入实参是指针

19.C++内存管理(大厂必问面试题,重要!重要!重要!

  • 栈区:栈中主要存放函数的局部变量,函数参数,返回地址等等,栈空间一般由操作系统进行默认分配或者程序指定分配,栈空间在进程声明周期一直都存在,当进程退出时,操作系统才会对栈空间进行回收。(栈空间一般由操作系统分配回收
  • 堆区:动态申请的内存空间,就是由malloc函数(c语言)或者new函数(c++语言)分配的内存块,由程序控制它的分配和释放,可以在程序运行周期内随时进行申请和释放,如果进程结束还没有释放,操作系统将回自动回收。(堆空间由我们程序员自己手动分配回收

备注:malloc函数申请堆空间,与之对应free函数释放空间(C特有的);new函数申请堆空间,与之对应的delete函数释放空间(C++特有的)。(new与delete用法将在中阶篇进行深入探讨)

  • 全局区/静态存储区:主要是存放全局变量与静态变量,程序运行结束后,操作系统自动释放。(全局区/静态存储区由操作系统进行回收

备注:静态(static)修饰变量为静态变量,static关键字在中阶篇进行深入探讨

  • 常量存储区:存放的是常量,不允许修改,程序运行结束自动释放。

备注:const修饰变量作为常量,const关键字将在中阶篇进行深入探讨

  • 代码区:存放代码,不允许修改,但可以执行。编译后的二进制存放在这里。
  • 内存泄漏(大厂面试高频题):程序中已动态分配的堆内存由于某种原因程序未释放,或者无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重问题。(通常情况下,一般是程序员未进行及时释放导致的内存泄漏)

例如:当使用new函数动态申请一块内存后,并未及时使用delete函数进行释放回收,久而久之,内存泄漏就相当严重了。

解决内存泄漏方法:智能指针

智能指针:使用时分配内存,不使用时,自动释放内存。(减少程序员担心内存泄漏的烦恼)

备注:智能指针将在高阶篇中进行深入探讨

结束语:本文较适合有一点点基础C语言的人,通过本文可以查缺补漏,当然作者的功底有限,本文也有可能不太好的地方,或者不完善的地方,也请大家在阅读本文的同时,能够提出批评和建议,我会进行弥补以及纠正。同时作者希望大家能够在阅读本文时,既能够收获知识还能收获快乐。最后作者希望大家能够热爱编程,不单是应付面试或者工作本身,更希望地是能够保持热爱,闪闪发光!!!

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 《Visual C从入门到精通PDF》是一本经典的教程书籍,适合想要学习和掌握Visual C编程的人群使用。这本书的目标是帮助读者从入门阶段逐步提高,最终达到精通Visual C的程度。 这本书的内容覆盖了Visual C编程的各个方面,包括基本的语法和概念、界面设计、事件处理、图形和图像处理、数据库连接等等。每个主题都有详细的讲解和示例代码,通过实践演示了如何在Visual C中进行各种操作和开发。 最初的几章主要介绍了Visual C的基本概念和环境设置,帮助读者熟悉开发环境并能够创建简单的项目。接下来的章节逐渐引入更多高级的概念和技术,如对象、类、继承和多态等。通过这些内容的学习,读者可以逐步掌握如何利用Visual C进行面向对象的编程。 书中还特别强调了实际项目开发经验和技巧,通过一些实际案例来演示如何应用所学的知识解决具体的问题。这对于读者来说非常有价值,可以帮助他们更好地理解和应用所学的内容。 总的来说,《Visual C从入门到精通PDF》是一本全面而实用的教程书籍,适合想要系统学习和掌握Visual C编程的读者使用。通过阅读和实践,读者可以逐步提高自己的编程能力,并最终达到精通的水平。 ### 回答2: 《Visual C 从入门到精通》是一本全面介绍Visual C的书籍,以帮助读者从初学者逐步掌握Visual C的各方面知识。书中包括了Visual C的基础知识、语法规则、常用函数和库、图形用户界面设计等内容。 在《Visual C 从入门到精通》一书中,作者详细介绍了Visual C编程语言的历史和发展背景,以及其与C++的关系。读者可以了解到Visual C的编程环境和工具,如Visual Studio,以及如何配置和使用它们来进行编程。 书中还详细介绍了Visual C的基础知识,如变量、数据类型、运算符和控制结构等。读者可以学习如何使用这些基础知识来进行简单的程序开发和调试。 此外,《Visual C 从入门到精通》还介绍了Visual C的高级特性和技术。读者可以学习如何使用Visual C的图形库和API来进行图形用户界面设计,如窗口、按钮、菜单等。还可以学习如何使用Visual C来开发网络应用程序、数据库应用程序等。 最后,《Visual C 从入门到精通》还提供了一些实际的例子和项目,以帮助读者巩固所学的知识,并提供了一些实用的编程技巧和经验分享。 总的来说,《Visual C 从入门到精通》是一本适合初学者学习Visual C的入门教材,通过阅读该书,读者可以逐步掌握Visual C的基础知识和高级技能,成为一名熟练的Visual C编程专家。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱喝牛奶的奶牛ovo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值