C++ 编程语法积累

目录:

fill()函数

头文件:#include < algorithm >
1. 函数简介

  • 对一维数组a[n]的赋值,
    fill(a,a+n,1)
    第一个参数是起始地址
    第二个参数是我们需要结束的地方,但不会报告a[n],区间为[a[0],a[n])前闭后开
    第三个参数是我们需要的赋值,这个相对memset()就灵活很多
  • 对二维数组a[m][n]的赋值
    fill(a[0],a[0]+mn,1)
    参数的类型和上面的一样,这里唯一值得注意的是第一个起始地址,因为是二维数组,它的第一个数值的表达是 a[0][0],而它的地址就可以用a[0] 来表示。
    例如赋予字符型
    const char c=’.’;
    fill(a[0],a[0]+1010,c);

2. fill()与fill_n()的区别

fill_n函数的作用是:给你一个起始点,然后再给你一个数值count和val。把从起始点开始依次赋予count个元素val的值。
注意: 不能在没有元素的空容器上调用fill_n函数
例题:给你n个数,然后输入一些操作:start,count,paint。表示从start开始连续填充count个数字,paint为填充的数值。

3. 举例:为二级指针赋值

	int **p = new int*[3];
	for(int i=0;i<3;i++){
		p[i] = new int[3];
		fill_n(p[i],3,1);
	}

或者

	int **p = new int*[3];
	for(int i=0;i<3;i++){
		p[i] = new int[3];
		fill(p[i],p[i]+3,1);
	}

stack 和 queue

stack<Type> a;	//定义栈,Type为数据类型
s.push(x);		//把x放到栈顶
s.top();		//返回栈顶的元素,但不会删除
s.pop();		//删除栈顶的元素,但不会返回
s.size();		//返回栈中元素的个数
s.empty();		//检查栈是否为空,如果为空,返回true,否则返回false

queue<Type> q;		//定义栈,Type为数据类型,例如int、float、char
q.push(x);		//把x放进队列
q.front();		//返回队首元素,但不会删除
q.pop();		//删除队首元素
q.back();		//返回队尾元素
q.size();		//返回元素个数
q.empty();		//检查队列是否为空		

优先队列priority_queue

头文件:#include< queue >
一、常用函数

  • top()
  • empty()
  • size()
  • push()
  • pop()

二、创建:priority_queue<type,cotainer,function> ( cotainer默认vector,function默认大顶堆 )

  • priority_queue<int,vector,greater< int > > 小顶堆
  • priority_queue<int,vector,less< int > > 大顶堆
  • priority_queue<pair<int,int>,vector< pair<int,int> >,cmp> (说明:cmp需要自定义,默认先比较first后比较second)

三、自定义比较函数

  • sort()函数有三种自定义方式: 重载小于操作符,自定义cmp函数,自定义仿函数。
  • priority_queue只能选择重载小于操作符和自定义仿函数。
// 重载小于操作符(class中也如此)
// 选择此方法,创建priority_queue时,function就可以省略
struct stu{
    string name;
    int age;
    bool operator<(const stu& p)const{
        return age < p.age;       // 大顶堆
        // 此处的大于号或小于号,与单独使用sort()函数相反
        // 如果这是sort函数自定义比较函数的话
        // 此处的小于号代表着从小到大排序
    }
}

// 自定义仿函数
// 选择此方法,创建priority_queue时,function处填写cmp
struct cmp{
    bool operator<(const stu& p,const stu& q){
        return p.age < q.age;       // 大顶堆
    }
}

自定义java中的split函数

头文件:#include

// 将字符串s以","分隔开存放到list中
    list<string> split(string s){
        list<string> lis;
        char* token = strtok(s.data(),",");
        while(token!=NULL){
            lis.push_back(token);
            token = strtok(NULL, ",");
        }
        return lis;
    }

to_string()函数

头文件:#include< string >
函数原型

  • string to_string (int val);
    string to_string (long val);
    string to_string (long long val);
    string to_string (unsigned val);
    string to_string (unsigned long val);
    string to_string (unsigned long long val);
    string to_string (float val);
    string to_string (double val);
    string to_string (long double val);

功能:将数值转化为字符串。返回对应的字符串。

C++11:增加了全局函数std::to_string,以及std::stoi/stol/stoul/stoll/stoull/stof/stod/stold等函数。

  • stoi:string型变量转换为int型变量
    stol:string型变量转换为long型变量
    stoul:string型变量转换为unsigned long型变量
    stoll:string型变量转换为long long型变量(常用)
    stoull:string型变量转换为unsigned long long型变量
    stof:string型变量转换为float型变量
    stod:string型变量转换为double型变量(常用)
    stold:string型变量转换为long double型变量

memcpy()函数 (头文件#include<string.h>)
这个函数我把它当作 java 中的 clone() 函数来用。
原形:void *memcpy(void *dst, const void *src, size_t n);

  • 从存储区 src 复制 n 个单位到存储区 dst

示例代码如下:

#include<bits/stdc++.h>
using namespace std;
int main() {
	int a[][2] = {
		{1,2},
		{3,4}
	};
	int b[2][2];
	memcpy(b, a, sizeof a);
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 2; j++) {
			cout << b[i][j] << " ";
		}
		cout << endl;
	}

	int** m = new int* [2];
	for (int i = 0; i < 2; i++) {
		m[i] = new int[2];
		fill_n(m[i], 2, 1);
	}
	int** n = new int* [2];
	for (int i = 0; i < 2; i++) {
		n[i] = new int[2];
		memcpy(n[i], m[i], sizeof(int)*2);
	}
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 2; j++) {
			cout << n[i][j] << " ";
		}
		cout << endl;
	}
	return 0;
}

/*
输出:
     1 2
     3 4
     1 1
     1 1
*/

sort()函数

参考链接:
C++ sort函数中cmp()比较函数的写法
总结C++自定义比较函数(算法:sort,容器:priority_queue、map、set)

string,char*与char[]

string -> 栈(变量)+堆(内容);
char* = “” -> 栈+常量区;
char[] -> 栈

参考如下:
关于char[]和char*的理解
字符串变量、字符串常量、string变量在内存中的存储分析
string 是否以‘\0’结尾 讨论

free()和delete

c的free和c++的delete的区别
C++之使用new和delete动态申请和释放二维数组
C++ 中free与delete的区别

cin,cin.get()和cin.getline()

get与getline不同的是就是get不清缓存

#include<bits/stdc++.h>
using namespace std;
int main() {
	char a[20];
	char b[20];
	cin.get(a,20, '1');
	cin.get(b,10);
	cout << a << endl;
	cout << b << endl;
	return 0;
}
/*
aaa111b
aaa
111b
*/

参考如下:
cin, cin.getline(), cin.get()你都搞懂了吗

sscanf()

sscanf是用来分析字符串的

举例:

char buf[20];
sscanf("123<zzz>345", "%*[^<]<%[^>]", buf);
// buf里面是zzz

参考如下:
百度百科

函数前后加const的区别

  1. 函数前加const:普通函数或成员函数(非静态成员函数)前均可加const修饰,表示函数的返回值为const,不可修改。格式为:
const returnType functionName(param list)
  1. 函数后加const:只有类的非静态成员函数后可以加const修饰,表示该类的this指针为const类型,不能改变类的成员变量的值,即成员变量为read only,任何改变成员变量的行为均为非法。此类型的函数可称为只读成员函数,格式为:
returnType functionName(param list) const

说明:类中const(函数后面加)与static不能同时修饰成员函数,原因有以下两点。

① C++编译器在实现const的成员函数时,为了确保该函数不能修改类的实例状态,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的,也就是说此时const的用法和static是冲突的;

②两者的语意是矛盾的。static的作用是表示该函数只作用在类型的静态变量上,与类的实例没有关系;而const的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系,因此不能同时用它们。

  1. const成员函数与const对象

const成员函数还有另外一项作用,即常量对象相关。对于内置的数据类型,我们可以定义它们的常量,对用户自定义的类类型也是一样,可以定义它们的常量对象。有如下规则:

① const对象只能调用后const成员函数;
② 非const对象既可以调用const成员函数,又可以调用非const成员函数。

摘自:https://blog.csdn.net/Gorgeous_mj/article/details/90574796

typedef define const的区别

  1. 定义

define定义后面不用加分号(#define为一宏定义语句),并且它的别名在对象的前面。它本身并不在编译过程中进行,而是在这之前(预处理过程)就已经完成了,但也因此难以发现潜在的错误及其它代码维护问题。

#define   INT             int
#define   TRUE         1
#define   Add(a,b)     ((a)+(b));
#define   Loop_10    for (int i=0; i<10; i++)

typedef需要加分号,并且它的别后面替换对象的前面。它是语言编译过程的一部分,但它并不实际分配内存空间。

typedef    int       INT;
typedef    int       ARRAY[10];
typedef   (int*)   pINT;

宏发生在预处理阶段,属于直接替换,没有分配内存空间,const分配空间;宏没有类型检查,而且不能调试,所以c++中常用const替换。

const int a = 1
  1. typedef与#define的区别

从以上的概念便也能基本清楚,typedef只是为了增加可读性而为标识符另起的新名称(仅仅只是个别名),而#define原本在C中是为了定义常量,到了C++,const、enum、inline的出现使它也渐渐成为了起别名的工具。有时很容易搞不清楚与typedef两者到底该用哪个好,如#define INT int这样的语句,用typedef一样可以完成,用哪个好呢?我主张用typedef,因为在早期的许多C编译器中这条语句是非法的,只是现今的编译器又做了扩充。

为了尽可能地兼容,一般都遵循#define定义“可读”的常量以及一些宏语句的任务,而typedef则常用来定义关键字、冗长的类型的别名。

宏定义只是简单的字符串代换(原地扩展),而typedef则不是原地扩展,它的新名字具有一定的封装性,以致于新命名的标识符具有更易定义变量的功能。

typedef    (int*)      pINT;
#define    pINT2    int*

效果相同?实则不同!实践中见差别:pINT a,b;的效果同int *a; int *b;表示定义了两个整型指针变量。而pINT2 a,b;的效果同int *a, b;表示定义了一个整型指针变量a和整型变量b。

参考:C++中typedef和define的区别define 与typedef的区别const、typedef和#define的用法与区别

vector

  • 初始化:在C++11中,vector的初始化和等号赋值都出现了这样的语法。
vector<int> nums({1, 2, 3, 4, 5});
vector<int> nums3 = vector({1, 2, 3, 4, 5});
vector<int> nums2;
nums2 = {1, 2, 3, 4, 5};
  • insert()
// 在指定位置loc前插入值为val的元素,返回指向这个元素的迭代器
iterator insert( iterator loc, const TYPE &val );
// 在指定位置loc前插入num个值为val的元素
void insert( iterator loc, size_type num, const TYPE &val );
// 在指定位置loc前插入区间[start, end)的所有元素
void insert( iterator loc, input_iterator start, input_iterator end );
在指定位置loc前插入num个值为val的元素
// 在迭代器 pos 指定的位置之前,插入初始化列表(用大括号{}括起来的多个元素,中间有逗号隔开)中所有的元素,并返回表示第一个新插入元素位置的迭代器
iterator insert(pos,initlist)

vector和map的erase函数

迭代器失效问题:在 C++ 中,当容器中的元素发生插入、删除或移动等操作时,容器内部的数据结构可能会发生改变,这可能会导致之前保存的迭代器失效,即不能再正确地指向原来的元素或位置,因此使用失效的迭代器可能会引发未定义行为,包括程序崩溃、数据损坏等问题。

map下erase函数原型

# std::map 提供了三个重载的erase函数,分别是:

# 删除迭代器position指向的元素,并返回指向被删除元素后面元素的迭代器
iterator erase(iterator position)

# 删除所有键值为key的元素,并返回删除的元素个数
size_type erase(const key_type& key)

# 删除[first, last)范围内的所有元素,并返回指向被删除元素后面元素的迭代器
iterator erase(iterator first, iterator last)

/* 
* 这三个重载函数的参数和返回值分别为:
* position:要删除的元素的迭代器。
* key:要删除的元素的键值。
* first:要删除的范围的起始迭代器。
* last:要删除的范围的结束迭代器(不包括在范围内)。
* 返回值:指向被删除元素后面元素的迭代器或者删除的元素个数。
* /

按我们理解的是下面代码会把 mp[‘b’] 删除,但是输出结果一个没删,原因是++it时it已经失效了,最后导致未定义的行为,可能会导致程序崩溃或者出现其他错误。

int main(){
    map<char, int> mp = {{'a', 1}, {'b', 2}, {'c', 3}, {'d', 4}};
    auto it = mp.find('b');
    mp.erase(it,++it);
    # 可以借助next函数和advance函数去写
    # 例如:mp.erase(it,next(it,2));
    # 删除 mp['b'] 和  mp['c'] 
    for(it=mp.begin(); it!=mp.end();it++){
        cout << it->first << " " <<  it->second << endl;
    }
    return 0;
}

由此引发了下一个问题,第一段代码是错误的,第二段代码是正确的。

# 第一段代码
int main(){
    map<char, int> mp = {{'a', 1}, {'b', 2}, {'c', 3}, {'d', 4}};
    for(auto it=mp.begin(); it!=mp.end();it++){
        cout << it->first << " " <<  it->second << endl;
        mp.erase(it); # 这里it已经失效,由此不能再执行it++操作
    }
    return 0;
}
# 第二段代码
int main(){
    map<char, int> mp = {{'a', 1}, {'b', 2}, {'c', 3}, {'d', 4}};
    for(auto it=mp.begin(); it!=mp.end();){
        cout << it->first << " " <<  it->second << endl;
        mp.erase(it++);
    }
    return 0;
}

正确说明:mp.erase(it++) 的返回值是一个指向被删除元素之后的元素的迭代器,也就是说,it++ 返回的是指向当前元素的迭代器,而 mp.erase 函数返回的是指向被删除元素之后的元素的迭代器。

假设当前迭代器 it 指向的元素是 mp 中的第 i 个元素,那么执行 mp.erase(it++) 的过程如下:

  1. it++ 返回指向第 i 个元素的迭代器 it,并将 it 向后移动一位,指向第 i+1 个元素。
  2. mp.erase(it) 删除第 i 个元素,并返回指向第 i+1 个元素的迭代器。
  3. 将 it 赋值为 mp.erase(it) 的返回值,即指向第 i+1 个元素的迭代器。

这样,下一次迭代时,it 就指向了被删除元素之后的元素,而不会指向已经被删除的元素,避免了迭代器失效的问题。

这么想更好:后缀自增运算符会创建一个临时对象。

  • 后缀自增运算符 it++ 会先返回迭代器 it 的原始值,然后再将 it 的值加 1。这个过程中,为了保存迭代器 it 的原始值,会创建一个临时对象,这个临时对象保存了 it 的原始值,但是它并不是 it 本身。
  • 具体来说,当 it 指向第一个元素时,执行 it++ 会创建一个临时对象,保存 it 的原始值,然后 it 的值加 1,此时 it 指向第二个元素。

下面看一下vector。

# std::vector 中的 erase 函数有多个重载版本,具体如下

# 删除迭代器 pos 指向的元素,返回指向被删除元素之后元素的迭代器
iterator erase(iterator pos)

# 删除迭代器范围 [first, last) 内的元素,返回指向被删除元素之后元素的迭代器
iterator erase(iterator first, iterator last)

# 删除下标为 pos 的元素
void erase(size_type pos)

# 删除下标范围 [first, last) 内的元素
void erase(size_type first, size_type last)

/*
* 其中,前两个版本接受迭代器参数,可以删除任意位置的元素;
* 后两个版本接受下标参数,只能删除指定下标的元素,不能删除任意位置的元素。
* 另外,前两个版本返回一个迭代器,指向被删除元素的下一个位置,可以用于遍历容器;
* 后两个版本没有返回值,只能用于删除指定下标的元素。
*/

如下写法是错误的:因为vector中的erase会把后续节点迁移,下面的方法相当于 it 每一次循环加了两次。

int main(){
    vector<int> vec = {1,2,3,4,5};
    for(auto it=vec.begin(); it!=vec.end();it++){
        cout << *it << endl;
        vec.erase(it);
    }
    return 0;
}

摘自别人

在工作中,有些常用语句拈手即来,比如 m_testList.erase(it++); 虽然erase会导致迭代器失效,但是it++就可以保证it指向循环的下一个位置。因为it++实际上erase调用的是拷贝构造的一个临时迭代器,it在进行删除操作之前已正确指向下一个对象。在以前多次使用,都没有出现问题,今天却突然遇到了bug。

经验证,erase在map、list等容器中,使用erase(it++);方式循环删除操作完全没有问题;但是在vector是不支持的。个人理解:vector在擦除后,因为是顺序存储,所以后面的位置前移;it++后已经指向下一个对象的位置了,之后线性存储数据前移,导致跳过一个元素,如果跳过的碰巧是end(),那么就当然会core了。当然这种默认变动,是和编译器有关,部分编译器可能会优化可能不会,所以靠谱的写法应该是it = m_testList.erase(it);

然而在map中,这种语句却又不支持,map的erase是没有返回迭代器下一个位置。

所以两种写法,在工作中都会用到。需要特别注意就是循环删除的时候,iterator不要两次++。

原文链接:https://blog.csdn.net/Andrewbupt/article/details/9768185

C++ 从函数返回数组

注意以下代码是不允许的,因为当函数结束时,局部数组将被销毁,指向它的指针将变得无效。

int* myFunction()
{
   int myArray[3] = {1, 2, 3};
   return myArray;
}

为了避免以上情况,你可以使用静态数组或者动态分配数组。

使用静态数组需要在函数内部创建一个静态数组,并将其地址返回,例如:

int* myFunction()
{
   static int myArray[3] = {1, 2, 3};
   return myArray;
}

使用动态分配数组需要在函数内部使用 new 运算符来分配一个数组,并在函数结束时使用 delete 运算符释放该数组,例如:

int* myFunction()
{
   int* myArray = new int[3];
   myArray[0] = 1;
   myArray[1] = 2;
   myArray[2] = 3;
   return myArray;
}
int main() {
    int* myArray = createArray(5);
    for (int i = 0; i < 5; i++) {
        cout << myArray[i] << " ";
    }
    cout << endl;
    delete[] myArray; // 释放内存
    return 0;
}

当使用动态分配数组时,调用函数的代码负责释放返回的数组。这是因为在函数内部分配的数组在函数结束时不会自动释放。

C++ 内存分配方式

C/C++主要有以下四种内存存储区:

  1. 全局区:主要存放的数据有:全局变量、静态变量、常量(如字符串常量)
    全局区的叫法有很多:全局区、静态区、数据区、全局静态区、静态全局区
    这部分可以细分为data区和bss区
  • data区:主要存放的是已经初始化的全局变量、静态变量和常量
  • bss区:主要存放的是未初始化的全局变量、静态变量,这些未初始化的数据在程序执行前会自动被系统初始化为0或者NULL
  • 常量区:常量区是全局区中划分的一个小区域,里面存放的是常量,如const修饰的全局变量、字符串常量等
    ( 全局/静态存储区域:存全局变量,静态变量。程序编译时内存已分配好,并存在于程序整个运行期间,程序结束后由系统统一释放;)
  • 补充:全局变量和静态变量被分配到同一块内存中。
    ----C 语言中,全局变量又分为初始化的和未初始化的。初始化的全局变量和静态变量在一块区域,未初始化的全局变量与静态变量在相邻的另一块区域。同时未被初始化的对象存储区可以通过 void* 来访问和操纵,程序结束后由系统自行释放。
    ----C++ 里面没有区分,他们共同占用同一块内存区。
  1. 栈:存放函数的参数值,局部变量,函数执行结束时会被自动释放。栈内存分配运算内置于处理器的指令集中,效率高,但是容量有限;

  2. 堆(动态内存分配):通过new和malloc由低到高分配,由delete或free手动释放或者程序结束自动释放。动态内存的生存期人为决定,使用灵活。缺点是容易分配/释放不当容易造成内存泄漏,频繁分配/释放会产生大量内存碎片。 若程序员不释放,程序结束时可能由OS(操作系统)回收;

  3. 字符/文字常量区: 存放常量字符串,程序结束时由系统释放;

  4. 程序代码区: 存放函数体的二进制代码。

C++ 程序编译过程

整个编译过程分为两大步:

1)编译 :把文本形式的源代码翻译成机器语言,并形成目标文件。

2)连接 :把目标文件、操作系统的启动代码和库文件组织起来形成可执行程序。

1. 编译

细分为3个阶段:

1.1)先进行编译预处理

预处理又称为预编译,是做些代码文本替换工作。编译器执行预处理指令(以#开头,例如#include)。这个过程会得到不包含#指令的 .i 文件。这个过程会拷贝 #include 包含的文件代码,进行 #define 宏定义的替换 ,处理条件编译指令 (#ifndef #ifdef #endif)等。

1.2)编译优化

通过预编译输出的.i文件中,只有常量:数字、字符串、变量的定义,以及c语言的关键字:main、if、else、for、while等。这阶段要做的工作主要是,通过语法分析和词法分析,确定所有指令是否符合规则,之后翻译成汇编代码。

这个过程将.i文件转化位.s文件。

1.3) 汇编

汇编过程就是把汇编语言翻译成目标机器指令的过程,生成目标文件(.obj .o等)。目标文件中存放的也就是与源程序等效的目标的机器语言代码。

目标文件由段组成,通常至少有两个段:

代码段:包括主要程序的指令。该段是可读和可执行的,一般不可写。

数据段:存放程序用到的全局变量或静态数据。可读、可写、可执行。

这个过程将.s文件转化成.o文件。

2.连接过程

由汇编程序生成的目标文件并不能立即就执行,还要通过链接过程。

原因:1) 某个源文件调用了另一个源文件中的函数或常量

  1. 在程序中调用了某个库文件中的函数

链接程序的主要工作就是将有关的目标文件连接起来。

静态链接和动态链接的区别 还需要深入学习。。。待补充

这个过程将.o文件转化成可执行的文件。

拷贝构造函数和移动构造函数

拷贝有两种:深拷贝,浅拷贝。

当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。

深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。

有时候我们会遇到这样一种情况,我们用对象a初始化对象b,后对象a我们就不在使用了,但是对象a的空间还在呀(在析构之前),既然拷贝构造函数,实际上就是把a对象的内容复制一份到b中,那么为什么我们不能直接使用a的空间呢?这样就避免了新的空间的分配,大大降低了构造的成本。这就是移动构造函数设计的初衷。

摘自:拷贝构造函数vs移动构造函数C++11——移动构造函数及std::move() 的使用

C++ extern

C++ extern

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

-月光光-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值