c++语法总结

基本语法

数组、指针、初始化等

数组

数组的初始化

int a[5] = {};    //默认初始化,全部为0
int a[5] = {0};    //全部初始化为0
int a[5]{};    //c++11新写法,默认初始化0
int a[5] = {1};    //[1, 0, 0, 0, 0],第一位1,其余默认初始化0
int a[5] = {1,1,1,1,1}; //初始化为1
int value[9][9] = {{1,1},{2}}//value[0][0,1]和value[1][0]的值初始化,其他初始化为0

动态数组初始化

int *pia = new int[10]; // 每个元素都没有初始化,一定要 delete []pia
int *pia2 = new int[10] ();  // 每个元素初始化为0

int (*value)[n] = new int[m][n];
delete []value; // n必须为常量,调用直观。未初始化

string

初始化方法

string temp(haystack.substr(i, len)); //截取另一个字符串的字串,i起始位置,len是长度

string str;
getline(cin,str); //从控制台输入字符串的方法

static静态变量

在C++中,全局static变量和class的static成员变量在main函数之前初始化,main函数之后销毁
函数内部的局部static变量在该函数第一次被调用时初始化,在main函数之后销毁
对于static数据即使是函数内部的对象则存放在全局数据区,全局数据区的数据并不会因为函数的退出就将空间释放。
在这里插入图片描述
为什么答案是D不是B,这属于函数内部的情况,函数第一次被调用时被初始化,直至main函数结束,潜在意思就是,当函数第二次被调用时,就不会再重复声明了,声明这句话不再走了。
这种情况还是有的时候被需要的,如回调函数里面,一个什么request结构、句柄什么的,不想被重复声明,可以用static关键字。

模板

模板是泛型编程的基础,泛型编程是独立于任何数据类型的编程方式,stl的vector、map、stack等都可以使用不同的数据类型

模板函数

函数返回值,或函数参数均可以是模板类型

template<typename T>
inline T const& Max(T const&a, T const& b)
{
    return a < b ? a : b;
}
cout << "Max(i,j): " << Max(3,6) << endl;

类模板

类中的成员变量,函数参数返回值等都可以是模板类型,下面是使用vector实现stack的例子

#include<iostream>
#include<vector>
#include<cstdlib>
#include<string>
#include<stdexcept>
using namespace std;

template<class T>

class Stack
{
    private:
        vector<T> elems;
    public:
        void push(T const&);
        void pop();
        T top() const;
        bool empty() const
        {
            return elems.empty();
        }
};

template<class T>
void Stack<T>::push(T const& elem)
{
    elems.push_back(elem);
}

template <class T>
void Stack<T>::pop()
{
    if(elems.empty())
    {
        throw out_of_range("Stack<>::pop():empty stack");
    }
    elems.pop_back();
}

template<class T>
T Stack<T>::top() const //const修饰类成员函数,表示成员变量不能被修改,不能修饰一般函数体!
{
    if(elems.empty())
    {
        throw out_of_range("Stack<>::pop():empty stack");
    }
    return elems.back();
}

int main()
{
    try
    {
        Stack<int> intStack;
        Stack<string> stringStack;

        intStack.push(7);
        cout << intStack.top() << endl;

        stringStack.push("hello");
        cout << stringStack.top() << endl;
        stringStack.pop();
        stringStack.pop();
    }
    catch(exception const& ex)
    {
        cerr << "Exception: " << ex.what() << endl;
        return -1;
    }
}

命名空间

大型程序往往会使用多个独立开发的库,这些库会定义大量的全局名字,如类、函数和模板等,不可避免会出现某些名字相互冲突的情况。命名空间namespace分割了全局命名空间,其中每个命名空间是一个作用域

namespace foo {
    class Bar { /*...*/ };
}  // 命名空间结束后无需分号

//嵌套的命名空间
namespace foo {
    namespace bar {
        class Cat { /*...*/ };
    }
}// 调用方式
foo::bar::Cat  //嵌套调用方式
// 三种调用方式
test :: func1();

using test :: func1;//单独声明命名空间中的func1函数
func1();            //直接使用func1,无须添加其他的前缀

using namespace test  //将整个命名空间全部声明
func1();            //访问空间内的函数1
func2();            //访问空间内的函数2

默认命名空间

默认命名空间又叫全局命名空间。典型的默认命名空间就是main函数,函数或变量没有放在其他的命名空间中,编译器会把它们归类到一起组成一个命名空间。假如我们的程序一个namespace都没有定义,那么所有的变量和函数都在一个空间内

::func();  //全局空间,前面两个引号

//如果两个文件中,分别有a::b{}以及b{}两个namespace,a::b中引用b中的内容,如下
namespace a::b {
  ::b::func()  //应显式注明,此处的b为全局作用于下的命名空间,防止和a::b冲突
}

STL常见库

vector、stack、map、queue等常见库的基本使用方法

vector

初始化方法

vector<vector<int>> newOne(r, vector<int>(c, 0));//二维vector初始化尺寸方法!
vector<vector<int>> graph(numCourses,vector<int>(0,0));
vector<int> list1(list2);//拷贝方式
vector<int> list(6, 0);//长度为6,初始化为0,第二个参数也可以不填

截取部分元素

vector<int>::iterator first1 = nums.begin();
vector<int>::iterator last1 = nums.begin() + 2; //截取nums 0 1两个位置的元素!
vector<int> leftNums(first1, last1);

容器最后添加元素.push_back(element),删除最后一个元素.pop_back(),删除特定元素.erase(iter),iter是迭代器指针

清空、插入

a.clear()
a.pop_back()  //删除最后一个元素

std::vector<int> demo{1,2};
//第一种格式用法
demo.insert(demo.begin() + 1, 3);//{1,3,2}

排序

vector<int>a{2,5,1,4,6};
//正向排序
sort(a.begin(),a.end());
for(auto i:a)
{
  cout<<i<<" ";
}
cout<<endl;
//反向排序
sort(a.rbegin(),a.rend());
for(auto i:a)
{
  cout<<i<<" ";
}
cout<<endl;

//带cmp参数的排序
sort(a.begin(),a.end(),cmp);
for(auto i:a)
{
  cout<<i<<" ";
}

deque

eque和vector很类似,也是采用动态数组来管理元素。与vector不同的是deque的动态数组首尾都开放,因此能够在首尾进行快速地插入和删除操作
在这里插入图片描述

std::deque的构造方法:
std::deque<int> queInt;//空队列
std::deque<int> queInt(10;//长度为10的队列(其中元素被初始化为该类型的0值)
std::deque<int> queInt(105;//长度为10的队列(其中元素被初始化为该类型的5)

std::deque<int> queIntTemp(105;//长度为10的队列(其中元素被初始化为该类型的5)
std::deque<int> queInt(queIntTemp);//以queIntTemp初始化queInt

int nArray[5] = {0,1,2,3,4};
std::deque<int> queInt(nArray, nArray + 5);//注意这里是nArray + 5,而不是nArray + 4

插入数据的方法
push_back();//末尾插入
push_front();//前端插入
insert(pos,elem)//特定位置插入

删除方法
pop_front();
pop_back();
erase();
clear();

pair与make_pair

pair主要的作用是将两个数据组合成一个数据,两个数据可以是同一类型或者不同类型,尤其在不同类型的时候使用,容器类别map和multimap就是使用pairs来管理其健值/实值(key/value)的成对元素。 make_pair()是可以构造pair数据类型的函数。

pair <string,double> product1 ("tomatoes",3.25);
pair <string,double> product2;
pair <string,double> product3;
	
product2.first ="lightbulbs"; // type of first is string
product2.second =0.99; // type of second is double
	
product3 = make_pair ("shoes",20.0);

map

判断map某个元素是否存在

if(1 == map.count(key))    //返回1存在,0不存在

map遍历与构建

std::map<string, string> mapSet;
mapSet.insert(pair<string, string>("中文", "Chinese"));//插入关键字和值
mapSet.insert(pair<string, string>("英文", "English"));
mapSet["日语"] = “Japanese";

std::map<string, string>::iterator  it;
for (it = mapSet.begin(); it != mapSet.end(); ++it)
{
    std::cout << "key" << it->first << std::endl;
    std::cout << "value" << it->second << std::endl;
}

优先队列

与普通队列区别:
在优先队列中,队列中的每个元素都与某个优先级相关联,但是优先级在队列数据结构中不存在。
优先队列中具有最高优先级的元素将被首先删除,而队列遵循FIFO(先进先出)策略,这意味着先插入的元素将被首先删除。
如果存在多个具有相同优先级的元素,则将考虑该元素在队列中的顺序。

priority_queue<int> variable_name;

其模板声明带有三个参数,priority_queue<Type, Container, Functional>, 其中Type为数据类型,Container为保存数据的容器,Functional为元素比较方式。Container必须是用数组实现的容器,比如 vector, deque. STL里面默认用的是vector. 比较方式默认用operator< , 所以如果把后面两个参数省略的话,优先队列就是大顶堆,队头元素最大。

priority_queue<int, greater<>> pq;//这是错误的
priority_queue<int,vector<int> , greater<>> pq;//这是对的
 
//升序队列
priority_queue <int,vector<int>,greater<int> > q;
//降序队列
priority_queue <int,vector<int>,less<int> >q;
//greater和less是std实现的两个仿函数(就是使⼀个类的使⽤看上去像⼀个函数。其实现就是类中实现⼀个operator(),这个类就有了类似函数的⾏为,就是⼀个仿函数类了)

函数

回调函数

回调函数本质上就是一个函数指针。为什么需要回调函数
自己的理解:如下面这种场景,有一个提供运算的线程或程序,有些客户需要计算加法,有些需要计算减法,有些需要其他更复杂的运算,一种办法是把这些函数的定义都加到服务程序里,这样可能就得频繁的更改,甚至编译等,也造成了耦合,另一种办法是注册回调函数,客户端将运算的函数指针传递给服务程序,让服务程序进行定制化运算服务。
简言之,你给他说怎么做(把函数指针给他),然后他来帮你做(再调用他)。也可以说 你“定制化”地调用他。

#include <stdio.h>
typedef int(*callback)(int,int);
int add(int a,int b,callback p){
    return (*p)(a,b);
}
int add(int a,int b){
    return a+b;
}
int main(int argc,char *args[]){
    int res = add(4,2,add);
    printf("%d\n",res);
    return 0;
}

修饰词

explicit

explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的

class CxString  // 没有使用explicit关键字的类声明, 即默认为隐式声明  
{  
public:  
    char *_pstr;  
    int _size;  
    CxString(int size)  
    {  
        _size = size;                // string的预设大小  
        _pstr = malloc(size + 1);    // 分配string的内存  
        memset(_pstr, 0, size + 1);  
    }  
    CxString(const char *p)  
    {  
        int size = strlen(p);  
        _pstr = malloc(size + 1);    // 分配string的内存  
        strcpy(_pstr, p);            // 复制字符串  
        _size = strlen(_pstr);  
    }  
    // 析构函数这里不讨论, 省略...  
};  
  
    // 下面是调用:  
  
    CxString string1(24);     // 这样是OK的, 为CxString预分配24字节的大小的内存  
    CxString string2 = 10;    // 这样是OK的, 为CxString预分配10字节的大小的内存  
    CxString string3;         // 这样是不行的, 因为没有默认构造函数, 错误为: “CxString”: 没有合适的默认构造函数可用  
    CxString string4("aaaa"); // 这样是OK的  
    CxString string5 = "bbb"; // 这样也是OK的, 调用的是CxString(const char *p)  
    CxString string6 = 'c';   // 这样也是OK的, 其实调用的是CxString(int size), 且size等于'c'的ascii码  
    string3 = string1;        // 这样也是OK的, 至少编译是没问题的, 但是如果析构函数里用

上面的代码中, “CxString string2 = 10;” 这句为什么是可以的呢?

在C++中, 如果的构造函数只有一个参数时, 那么在编译的时候就会有一个缺省的转换操作:将该构造函数对应数据类型的数据转换为该类对象. 也就是说 “CxString string2 = 10;” 这段代码, 编译器自动将整型转换为CxString类对象, 实际上等同于下面的操作:

CxString string2(10);  

智能指针

使用 raw pointer 管理动态内存时,经常会遇到这样的问题:
忘记delete内存,造成内存泄露。
出现异常时,不会执行delete,造成内存泄露。
智能指针就是解决这个问题的,在c++中,智能指针一共定义了4种:auto_ptr、unique_ptr、shared_ptr 和 weak_ptr

shared_ptr

shared_ptr是最常用的C++11提供的智能指针。shared_ptr是以类模板的方式实现的,shared_ptr采用了引用计数器,多个shared_ptr中的T *ptr指向同一个内存区域(同一个对象),并共同维护同一个引用计数器。shared_ptr定义如下,记录同一个实例被引用的次数,当引用次数大于0时可用,等于0时释放内存。

从而可以在任何地方都不使用时自动删除相关指针,从而帮助彻底消除内存泄漏和悬空指针的问题。
每个 shared_ptr 对象在内部维护着两个内存位置:
1、指向对象的指针。
2、用于控制引用计数数据的指针。
在这里插入图片描述
共享所有权如何在参考计数的帮助下工作的?
1、当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。
2、当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除该内存。
shared_ptr指针可以使用make_shared模板函数进行初始化,也可以直接使用new

auto spw1(std::make_shared<Widget>());     // with make func
std::shared_ptr<Widget> spw2(new Widget); // without make func
auto p1 = make_shared<int>(42); 
//如果定义在类里,.h中先声明,.cpp中再通过reset分配空间
std::shared_ptr<Widget> p_;   //.h文件
p_.reset(new Widget(paramList))  //.cpp文件,paramList为类的初始化成员列表

unique_ptr

unique_ptr是独享被管理对象指针所有权(owership)的智能指针。unique_ptr对象封装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。

auto upw1(std::make_unique<Widget>());     // with make func
std::unique_ptr<Widget> upw2(new Widget); // without make func
//以下是独享被管理对象指针的代码示例说明
void f1() {
    unique_ptr<int> p(new int(5));
    cout<<*p<<endl;
    unique_ptr<int> p2(p);
    unique_ptr<int> p3 = p;
}

上面的代码,p2、p3行会报错,unique_ptr没有复制构造函数,不支持普通的拷贝和赋值操作。因为unique_ptr独享被管理对象指针所有权。

相关工具

cmake

一种跨平台的代码管理工具,生成native的代码编译配置文件,如在linux写可以生成makefile文件。
基本语法-单目录

cmake_minimum_required(VERSION 3.10)

# set the project name and version
project(Tutorial VERSION 1.0)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# add the executable,生成的可执行文件(Tutorial,以及源文件列表
add_executable(Tutorial tutorial.cxx)

#头文件及头文件链接路径
configure_file(TutorialConfig.h.in TutorialConfig.h)
target_include_directories(Tutorial PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           )

多目录
目录结构如下,主目录main.cpp,以及math子目录,这时可以编写两个CMakeLists.txt,子目录生成库,供main.cpp调用

../demo3
├── CMakeLists.txt
├── main.cpp
└── math
	├── CMakeLists.txt
    ├── myMath.cpp
    └── myMath.h

主目录cmake文件

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)

# 项目信息
project (demo3)

# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)

# 添加 math 子目录,添加后子目录里的camkeList也会被处理
add_subdirectory(math)

# 指定生成目标 
add_executable(demo main.cpp)

# 添加链接库
target_link_libraries(demo MathFunctions)

子目录cmake文件,主目录定义的环境变量(用于指定头文件路径、链接库名称&路径等)在子目录仍有效

# 查找当前目录下的所有源文件,并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)

include_directories()
# 生成链接库
add_library (MathFunctions ${DIR_LIB_SRCS})
link_directories(${LIB_PATH}) #可以包含cv等库的路径等,这些括号里都可以加多个变量,或多个字符串,都会去找
target_link_libraries(MathFunctions
xxxxxx)    #可以指定依赖库的名字

几个常用语法

add_dependencies(a, b)  ##依赖的库,目的是告诉编译器要先把a b库编好,再来编译当前
INSTALL(TARGETS myrun mylib mystaticlib
       RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
       LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
       ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}  
)    ##将这些相关的二进制、库安装到指定目录
target_link_libraries(<target> ... <item>... ...)  #target必须是之前通过add_executable() or add_library()建立好的,后面的item是依赖库 

git

git checkout -b xxx    #新建分支
git checkout xxx    #切换分支,该分支已存在
git branch -a     #查看分支
git status
git add  dir_name   #准备提交或更改的目录
git commint -m "record log"    #提交修改
git pull
git push origin HEAD:branch_name

# 删除文件
git rm <filename>
git commit -m "commit message"
git push origin branch_name

# 如果修改的当前A分支,想要将代码更新到B分支,先push origin重定向,然后再正常add commit提交就行
git push origin HEAD:branch_B

修改了本地文件,切换分支时提示放弃或暂存修改,解决办法:添加链接描述
本地已有代码,进行更新,git pull origin branchname 更新代码
git删除了本地的一个目录,git pull拉不下来,可执行 git reset --hard HEAD
回退历史某个版本:git reset --hard 版本号。或者git log查看版本号,git checkout 版本号,切换即可
删除分支

git branch -d <BranchName>  #删除本地分支
git push origin --delete <BranchName>   #删除远程分支

git合并分支

#将branch_a 合并到dev
git checkout dev
git merge branch_a --no-ff  #--no-ff是禁止快进式合并,也可以不加
#如果冲突,会报合并冲突于某某文件,打开vscode解冲突,解冲突后再执行status然后add,并再次merge,之后正常提交就可以了
#根据提示,可能还需要git rm 或git add(dev有,而branch_a没有的)
#处理完冲突后,可以直接push到dev,如果没有权限,就需要checkout -b新建分支,然后提交到新建分支,然后再网页提交merge
git add 所有需要提交的文件
git commit -m "merge"
git merge branch_a 

#方法2
git checkout branch_name src/files  #将远程分支branch_name的src/files合并到当前本地分支,如果有冲突也需要到vscode解决

在vscode中点开源代码管理,可以看到有两个合并更改(冲突文件),利用右侧缩略图,下拉到出现冲突的位置,在蓝色和绿色代码中,选择保留哪部分的代码:如保留蓝色部分(点击Accept Incoming Change),这一个冲突就解决了;其他冲突也是一样的解决办法
在这里插入图片描述
git错误解决
切换分支时,报错:“以下未跟踪的工作树文件将被签出覆盖”,只需删除掉报错的目录或文件即可

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值