C++学习笔记

使用教材:《C++Primer》(第五版)电子工业出版社

  1. 与C语言不同,C++的整数型数组创建时不会默认将元素初始化为0,而是随机初始化为某个数
  2. 需要注意逻辑运算符&&||的短路特性,例如(a==b)||(++c==1)后面的C自增只有当前面为a==b为假时才会执行
  3. 枚举类型中的元素若赋初值的话一定要赋给它整型数值
  4. 输入流箭头向右,输出流箭头向左。cin>>score; cout<<"Hello World!"<<endl;
  5. 定义string对象的方法
// 1
string s1;
// 2
string s2="hahaha";
// 3
string s3(10,'@');//s3字符串由10个重复的@组成
// 4
string s4("hahaha");
// 5
string s5(s4);
  1. 使用cin将输入流输入字符串时,会忽略前面的空白(空格、换行、制表等),遇到空白时停止读取。当要读取一整行数据时,使用getline(cin, your variable name)
  2. while(cin>>your variable name)可以用于读取任意数量的数据,以ctrl+z加回车结束。
  3. 对于字符的操作,假设字符为c
函数结果
isalnum(c)若c是字母或数字时为真
isalpha(c)若c是字母时为真
isdigit(c)若c是数字时为真
islower(c)若c是小写字母时为真
ispunct(c)若c是标点符号时为真
isspace(c)若c是空白(空格、制表符、换行、回车)时为真
isupper(c)若c是大写字母时为真
tolower(c)返回c的小写形式
toupper(c)返回c的大写形式
  1. 在C++11中可以使用如下遍历元素的语法
for(char c:str)
	cout<<c<<","<<endl;
  1. 定义vector对象的方法
//1,初始化但v1不包含任何元素
vector<T> v1;
//2,初始化并将v1全部元素加入v2
vector<T> v2(v1);
//3,初始化并将n个val加入v3
vector<T> v3(n, val);
//4,初始化并包含n个默认的T类型值,如果T是int类型,则该默认值为0
vector<T> v4(n)
//5,初始化并将{}内元素依次加入v5
vector<T> v5{a,b,c...}
//6,初始化并将数组arr内所有元素加入v6,注意传入vector构造函数一定是指针类型
int arr[]={0,1,2,3,4,5};
vector<int> v6(begin(arr), end(arr));
  1. 注意区分如下几种定义方式(圆括号是标准的元素数量构造方法,而花括号是列表初始值初始化)
//1,v1中有10个元素,这10个元素默认值都为0
vector<int> v1(10);
//2,v2中只有一个元素:10
vector<int> v2{10};
//3,v3中有10个元素,每个元素都是1
vector<int> v2(10,1);
//4,v4中有两个元素,分别是10,1
vector<int> v2{10,1};
  1. 引用:本质就是变量的别名,声明形式auto &refer=i,refer是变量i的引用,也是变量i的别名,通过refer可以直接修改变量i的,引用的标志符号是&值。使用引用可以简洁的在循环中直接修改某些值。
    注意:当使用范围for语句遍历多维数组元素时,除了最后一层的范围for循环外其余的循环元素都要使用引用的形式,当然最后一层也可以用引用的形式,便于修改。如下代码2。因为如果不适用引用类型的话,系统会把外层变量识别为指针类型,而指针类型相当于数组第一个元素的指针,是不能进行范围for循环的,而引用类型是数组的别名,相当于数组可以进行范围for循环。
/*代码1*/
vector<int> v={0,1,2,3,4};
for(auto &i : v)	i*=i;
for(int i:v)	cout<<i<<endl;
/*代码2*/
int three_d[][][]={{{0,1},{2,3}},{{4,5},{6,7}}}
for(auto &i:three_d)
	for(auto &j:i)
		for(auto &k:j)	//for(auto k:j)
			cout<<k<<endl;
  1. 在向vector中添加新元素时,不能使用下标形式进行添加,例如
/*错误的添加方式*/
vector<int> v;
v[0]=1;
/*正确的添加方式*/
v.push_back(1);
//但,此时使用v[0]=2可以修改v[0]元素,所以下标形式可以进行修改但无法增加元素
  1. 在使用迭代器遍历容器内元素时,循环结构的控制条件最好写成for(auto it=v.begin(); it!=v.end(); it++),使用不等于而不是小于,因为可能某些容器的迭代器没有定义小于操作
  2. 最好在定义容器迭代器时使用auto类型,让程序自行判断迭代器的类型
  3. 迭代器访问数组元素需要加上*符类似于指针取值
  4. 在使用迭代器访问容器时,不要在访问时修改容器内元素,若修改的话会造成错误
  5. 不能直接把一个数组赋值给另一个数组,例如以下代码就是错误的
int a[3],b[3]={0,1,2};
a=b;
  1. begin()函数与end()函数能够返回一个容器的第一个元素的指针与最后一个元素下一个位置的指针。使用这两个函数可以实现数组遍历
for(int* i=begin(b);i<end(b);i++)
	cout<<*i<<endl;
  1. 注意区分括号对解引用运算的影响
int arr[]={0,1,2,3,4};
int a=*(arr+4);//a等于arr[4];
int b=*arr+4;//b等于arr[0]+4;
  1. 避免使用如a=a++这种未定义的语句
  2. 当使用成员访问符时,注意解引用运算符优先级低于点运算符,即(*p).size()*p.size()是不一样的,前者先对指针p解引用然后访问p引用的左值的成员,而后者先访问指针的成员然后对成员进行解引用
  3. switch语句的case标签一定需要是一个整数型常量表达式
  4. 异常处理例子
#include<iostream>
#include<exception>
using namespace std;
double division(int a, int b){
   if( b == 0 )	throw "Division by zero condition!";
   return (a/b);
}
int main(){
    string str = "string";
    try{
        char ch2 = str.at(100);
    }catch(exception e){
        cout<<"error!"<<endl;
    }
    int x=50,y=0;
    try {
        division(x,y);
    }catch (const char* msg) {
        cerr << msg << endl;
    }
    return 0;
}

结果

error!
Division by zero condition!
  1. 任意两个函数形参都不能同名,而且函数最外层作用域中的局部变量也不能使用与函数形参一样的名字。
  2. 若一个函数的形参是一个数组,那个最好使用数组指针作为形参,通过指针直接修改内存中的数据。
#include<iostream>
using namespace std;
void f(int* p){
    *(p+1)=10;
}
int main(){
    int arr[]={0,1,2,3,4,5,6,7,8,9};
    f(arr);
    for(int i:arr)
        printf("%d ",arr[i]);
    return 0;
}
  1. 当函数的形参为引用类型时,调用中任何对参数的操作都会影响引用类型所引用的变量。当函数不涉及对参数的修改时,使用引用减少参数传递的时间,但形参若是引用类型,则参数不能是字面值,只能是引用
  2. 参数传递的时候会忽略“非指针与非引用”实参的const属性(如果有的话),而指针类型与引用类型的const属性不会被忽略
/*输出结果:0*/
#include<iostream>
using namespace std;
int f(int i){
    i=0;
    return i;
}
int main(){
    const int i=1;
    cout<<f1(i);
    return 0;
}
  1. 在不同函数相互调用的时候,注意重复调用的形参的类型
#include<iostream>
using namespace std;
int f1(string s){
    return s;
}
int f(int i){
    return f1(i);
}
int main(){
    int i=1;
    cout<<f1(i);
    return 0;
}
  1. 当数组指针作为形参时,一般需要在函数形参中加入数组的长度,常用的方法两种:1.数组长度=end(arr)-begin(arr)2.直接在形参中加入数组开头指针与结束指针,分别是begin(arr),end(arr);或者使用数组的引用作为参数,这样一般不用传递数组长度,代码如void f(int (&arr)[10]),注意数组长度一定要加,这个数只要比数组长度长就行,因为需要不能省略&前面的括号与数组长度,所以这种方法一般不用
  2. 可变长参数使用方法,所有参数必须在花括号内用逗号隔开
#include<iostream>
using namespace std;
void f(initializer_list<string> list){
    for(string s:list)
        cout<<s<<endl;
}
int main(){
    f({"hi","i","am","tom"});
    return 0;
}
  1. sort函数所使用的比较函数,若该函数返回1则代表比较函数前面的形参要排在后面的形参前面,若返回-1则前面的形参排在后面的形参后面
  2. 千万千万千万注意不要在函数中返回一个局部变量,或者局部变量的引用、指针等
  3. 若函数返回一个变量的引用,则可以直接为函数返回的引用赋值
  4. 函数重载不允许两个函数的函数名、形参的数量及每个形参的类型都相同
  5. 因为参数传递会忽略非引用非指针实参的const属性,所以当两个函数非引用非指针形参类型与数目相同只不过一个是const一个是普通类型时,本质上这两个函数是一致的,无法进行重载。但如果形参时引用类型或指针类型时,const可以用于函数重载
  6. 若函数的某个形参被赋予了默认值,那个这个形参以后的所有形参都要有默认值
//正确的带默认值的声明
int f(int i1, int i2=1){
    return i1+i2;
//错误的带默认值的声明
int f(int i1=1, int i2){
    return i1+i2;
}
  1. 函数指针作为形参的函数调用实例
    函数名(不带括号)即可作为函数指针当作形参
#include<iostream>
using namespace std;
bool compare(int a,int b,bool(*p)(int a,int b)){
    return p(a,b);
}
bool isBigger(int a,int b){
    return a>b;
}
bool isSmaller(int a,int b){
    return a<b;
}
int main(){
    cout<<compare(1,2,isBigger)<<endl;
    cout<<compare(1,2,isSmaller)<<endl;
    return 0;
}
  1. C++的set容器,默认元素按照从小到大的顺序排序,如果要使用自定义类的set的话需要重载这个自定义结构体或者类的比较函数
    参考:https://blog.csdn.net/wzzfeitian/article/details/70171512
#include<iostream>
#include<set>
using namespace std;
struct song{
    int m_id;
    int m_hot;
    song(int id,int hot){
        this->m_id = id;
        this->m_hot = hot;
    }
    bool operator<(const struct song & right)const   //重载<运算符
    {
        if(this->m_id == right.m_id)//根据id去重
            return false;
        else if(this->m_hot != right.m_hot)
            return this->m_hot > right.m_hot;//降序
        else
            return this->m_id > right.m_id;
    }
};
int main(){
    set<song> mySet;
    song s1(10,100);
    song s2(20,200);
    song s3(20,300);
    song s4(30,200);
    mySet.insert(s1);
    mySet.insert(s2);
    mySet.insert(s3);
    mySet.insert(s4);
    for(auto it:mySet)
        cout<<"id:"<<it.m_id<<",hot:"<<it.m_hot<<endl;
    return 0;
}
  1. K-进制数
  • OJ链接:https://www.dotcpp.com/oj/problem1117.html
  • 题目描述:考虑包含N位数字的K-进制数. 定义一个数有效, 如果其K-进制表示不包含两连续的0。例:1010230 是有效的7位数,1000198 无效,0001235 不是7位数, 而是4位数。给定两个数N和K, 要求计算包含N位数字的有效K-进制数的总数。假设2 <= K <= 10; 2 <= N; 4 <= N+K <= 18。
  • C++代码(DFS思路):
    因为两个相邻数位不能都是零,所以可以从最高位开始进行分配数字,因为可以重复,所以这个数位从0开始或从1开始完全由上一位是否为0决定,所以在DFS函数中加入形参limit代表上一数位是否为0,若上一位为0则limit为1,否则为0。当分配的数位到了n-1时,就说明DFS到达终点,能到达终点的一定是合法的可以直接将result加一
#include<iostream>
using namespace std;
int n,k,result=0;
void dfs(int pos,int limit){
    if(pos==n-1)  result++;
    else{
        pos++;
        if(limit==1)
            for(int i=1;i<=k-1;i++) dfs(pos,0);
        else
            for(int i=0;i<=k-1;i++) dfs(pos,i==0);
    }
}
int main(){
    cin>>n>>k;
    for(int i=1;i<=k-1;i++) dfs(0,0);
    cout<<result<<endl;
    return 0;
}
  1. 如果碰到题目要求以回车结束输出的话,一般字符串使用getline保证字符串不会因为空格而断开
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值