Cpp-01-基础

建议:使用C++版本的C标准库头文件 C++标准库中除了定义C++语言特有的功能外,也兼容了C语言的标准库。C语言的头文件形如name.h,C++则将这些文件命名为cname。也就是去掉了.h后缀,而在文件名name之前添加了字母c,这里的c表示这是一个属于C语言标准库的头文件。因此,cctype头文件和ctype.h头文件的内容是一样的,只不过从命名规范上来讲更符合C++语言的要求。特别的,在名为cname的头文件中定义的名字从属于命名空间std,而定义在名为.h的头文件中的则不然。一般来说,C++程序应该使用名为cname的头文件而不使用name.h的形式,标准库中的名字总能在命名空间std中找到。如果使用.h形式的头文件,程序员就不得不时刻牢记哪些是从C语言那儿继承过来的,哪些又是C++语言所独有的。

1.默认类型转化

  • bool:只有0为false,其余均为true
  • 无符号数:超过范围则对数值总数取模后的余数。eg:8bit unsigned char可以表示0-255,如果赋值为-1,则为255(ps:-1的补码是255,反码为取反,补码则反码加1)
  • 有符号数:结果是undefined。

2.Cpp关键字

  • extern:头文件声明,表示变量不会重复包含。
  • inline:编译期间展开,减少运行开销,但是编译结果可能更大。
  • mutable:如果一个成员变量被声明为mutable,无论成员函数或对象时候为const,都能被修改。
  • explicit:禁止构造函数隐式转换,只对一个参数的构造函数有效,这里在类中声明构造函数使用。
  • noexcept:告诉编译器函数不会发生异常,可以提高部分性能(如果发生异常程序会终止)。
  • constexper:声明后由编译器验证变量是否是常量表达式。(注意:可以定义constexpr函数,但需要在编译期间可以计算值)
//限定符constexpr仅对指针有效,与指针所指的对象无关
//constexpr 可以指向非常量
int j = 0;
constexpr int i = 12;
void point_constexpr{
    // p是一个指向整形常量的指针,p可以修改,但是*P不可修改
    const int *p = nullptr;
    // q是一个指向整形变量的常量指针,q不可修改,但是*q可以修改
    constexpr int *q = nullptr;
    // np是一个指向整数的常量指针,其中为空
    constexpr int *np = nullptr;
    //i和j必须定义在函数体之外,否则报错,提示p访问运行时存储
    //因为constexpr要求表达式为常量,在编译时展开
    //p是常量指针,指向整形常量i
    constexpr const int *p2 = &i;
    //p1是常量指针,指向整数j
    constexpr int *p1 = &j;
}
  • auto:自动类型判断
int a = 0;
int b = 100;
auto c = a + b;
auto i = 0, *p = &i;
//一条声明语句只能有一个基本数据类型
//auto sz = 0, pi = 3.14;
const int ma = 1;
//auto 会忽略顶层const,但是会保留底层const
//可以通过const明确指出f为const int
const auto f = ma;
//auto 引用
auto &g = a;
//不能为非常量绑定字面值
//auto &h = 42;
//可以使用const
const auto &j = 42;
  • decltype:选择并返回操作数的数据类型,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
decltype(size()) sum;
const int ci = 0, &cj = ci;
//x is const int
decltype(ci) x = 0;
//y is const int&, y bind to x
decltype(cj) y = x;
//错误,z是一个引用,必须初始化
// decltype(cj) z;
//===================================
int i = 42, *p = &i, &r = i;
// b1 是一个int类型的引用
decltype(r) b1 = i;
// r+0 通过decltype返回int类型
decltype(r + 0) b2;
//错误,必须初始化,c是int&类型
// decltype(*p) c;

3.引用

  • 基础引用
int j = 20;
int &i = j;
j = 200;
cout << i <<" " << j << endl;
  • 指向指针的引用
int i = 42;
int *p;
//r是一个对p的引用
int *&r = p;
//给r赋值为&i,r就是p
r = &i;
//解引用r得到i,也就是p指向的对象,将i修改为0
*r = 0;

4.指针

  • 基础指针
int age = 18;
int *i = &age;
//age加2
*i += 2;
  • 指向常量的指针:不可以修改指向的内容的数据
const double pi = 3.14;
//普通指针不能指向常量
//double *ptr = &pi;
const double *cptr = *pi;
//不能给*cptr赋值
// *cptr = 42;
double dval = 3.14;
//常量指针可以指向非常量
cptr = &dval;
  • 常量指针:不可以将指针指向其他内存地址
int err = 0;
//cur将一直指向err
int const *cur = &err;
//不可以修改cur的指向,否则编译报错
int right = 1;
//cur = &right;
const double pi = 3.14;
//都是常量
const double const *pip = &pi;
//区别在于const的位置,靠近那个,那个就不能修改

5.常量

  • 基础概念
//常量必须初始化
const int bufsize = 512;
//常量无法修改,否则报错
//运行时初始化
const int i = get_size();
//编译时初始化
const int j = 12;
//可以使用常量初始化另外的常量
const int cj = j;
//常量引用,引用不能修改
const int &r1 = cj;
//不可以用非常量引用指向常量
// int &2 = cj;
//常量引用可以绑定普通int
int iv = 42;
const int &r2 = iv;

6.别名

//等价于 char*
typedef char *pstring;
//pstring是一个指向char的常量指针
const pstring cstr = 0;
//ps 是一个指针,其对象是指向char的常量指针
const pstring *ps;
char b = 'a';
//不可修改
// cstr = &b;
ps = &cstr;
const pstring cstr2 = &b;
ps = &cstr2;
//const 靠近pstring,所以不能修改pstring
//*ps = cstr;

7.string

//11111111111111111111111111111
//默认初始化,s1是一个空字符串
string s1;
//赋值初始化,s2是s1的副本
string s2 = s1;
//直接初始化 字面值初始化
string s3 = "hiya";
//直接初始化 构造函数初始化
//10个c
string s4(10, 'c');
string s5("hello zack");
//22222222222222222222222222222
//循环输入,直到换行符或者非法输入
string world;
while(cin >> world){
    cout << world << endl;
}
//读取一行
string linestr;
while(getline(cin, linestr)){
    cout << linestr << endl;
}
//每次读取一行,跳过空行
while(getline(cin, linestr)){
    if(!linestr.empty()){
        cout << linestr << endl;
        cout << linestr.size() << endl;
        string::size_type size = linestr.size();
    }
}
//string 重载了比较和+,operator+(){}
string a = "Hello", b = " world";
string c = a + b;


decltype(c.size()) punct_size = 0;
for(auto &i : c){
    //统计标点符号
    if(ispunct(i)){
        punct_size++;
    }
    //转大写
    i = toupper(i);
}
//把第一个单词变为大写
string sind("some string");
for (decltype(sind.size()) index = 0; index != sind.size() && isspace(sind[index]); ++index){
    sind[index] = toupper(sind[index]);
}

8.vector

vector是一种高效访问和修改的容器,支持遍历,索引访问。

void vector_init(){
    vector<string> v1{"a", "b", "c"};
    //初始化大小为10,值为-1
    vector<int> ivec(10,-1);
    vector<int> ivec1(10); //默认值0
    vector<string> v2(10); //默认值空
}
void vector_op(){
    vector<int> v1;
    for(int i = 0; i != 100; ++i){
        v1.push_back(i);
    }
    //求平方
    for(auto &i : v1){
        i *=i;
    }
}

9.迭代器

通过容器类的begin()和end()函数获取指向第一个元素位置的迭代器和指向最后一个元素下一个位置的迭代器。

  • *iter 返回iter所指对象得引用
  • iter->mem 解引用返回iter所指对象得mem成员
  • ++iter 迭代器位置后移,指向下一个元素
  • --iter 迭代器位置前移,指向上一个元素
  • iter1 == iter2 判断iter1和iter2是否相等
  • iter1 != iter2 判断iter1和iter2不相等
  • iter = iter + n 迭代器iter向后偏移n个元素
  • iter = iter -n 迭代器iter 向前偏移n个元素
  • iter1 >= iter2 迭代器iter1指向的元素是否在iter2之后
void iterator_func(){
    string s("Hello world!");
    for(auto i = s.begin(); i != s.end(); ++i){
        *i = toupper(*i);
    }
}
void iterator_const(){
    // it能读写vector<int>的元素
    vector<int>::iterator it;
    // it2能读写string对象中的字符
    vector<string>::iterator it2;
    // it3 只能读元素,不能写元素
    vector<int>::const_iterator it3;
    // it4 只能读字符,不能写字符
    vector<string>::const_iterator it4;

    vector<int> v;
    const vector<int> cv;
    // vit1的类型是vector<int>::iterator
    auto vit1 = v.begin();
    // vit2的类型是vector<int>::const_iterator
    auto vit2 = cv.begin();
    //通过cbegin和cend可以获取常量迭代器
    // cvit 类型为vector<int>::const_iterator
    auto cvit = v.cbegin();
}
//在通过迭代器遍历vector,string ,map等容器时
//添加元素或者删除元素会导致迭代器失效
//可以通过如下方式在遍历的同时删除元素
auto itdel = text.begin();
while (itdel != text.end()){
    if (itdel->empty())
        {
            itdel = text.erase(itdel);
            continue;
        }
    itdel++;
}
//等价于这个方法,下方更加高效
//使用 std::remove_if将符合条件元素移到容器的末尾,返回第一个符合的地址
//使用 erase 删除这些元素
text.erase(std::remove_if(text.begin(), text.end(), [](const std::string& str) { return str.empty(); }), text.end());
//二分查找
void iterator_device_find(){
    vector<int> orderv = {1,2,3,4,5,6,7,8,9,10};
    bool bfind = false;
    auto findit = orderv.begin();
    auto beginit = orderv.begin();
    auto endit = orderv.end();
    while(beginit != endit){
        auto midit = beginit + (endit - beginit) / 2;
        if(*midit == 9){
            findit = midit;
            bfind = true;
            break;
        }
        if(*midit > 9){
            endit = midit -1;
        }
        if(*midit < 9){
            beginit = midit + 1;
        }
    }
    if(bfind){
        cout << "find success, iter val is " << *findit << endl;
    }
}

10.数组

void arrary_init(){
    unsigned cnt = 42;
    constexpr unsigned sz = 42;
    const unsigned usz = 42;
    int arr[10];
    int *parr[sz];
    string strvec[usz];
    //必须常量才能初始化
    //string bad[cnt];
    const unsigned msz = 3;
    int a1[msz] = {1,2,3};
    int a2[] = {1,2,3};
    //等价于{0,1,2,0,0}
    int a3[5] = {0,1,2};
    string a4[4] = {"Hello","World"};//等价于加上“”
    //不允许用一个数组初始化另外的数组
    //int bada5[] = a3;
    //不允许用数组互相赋值
    //bada5 = a3;
    //===============复杂声明========
    int *ptr[10];
    arr = {1,2,3,4,5,6,7,8,9,0};
    //数组引用
    int(&arrRef)[10] = arr;
    //数组指针,通过(*parray)[0]或者parray[0][0]访问
    int(*parray)[10] = &arr;
}
void array_fun(){
    int scores[11] = {};
    int grade;
    while(cin >> grade){
        if(grade > 100){
            ++scores[grade / 10];
        }
    }
    //for
    for(auto i : scores){
        cout << i << " ";
    }
    cout<< endl;
}
void array_ptr(){
    int nums[] = {1,2,3};
    int *p = &nums[0];
    //num 是一个整数指针,指向nums第一个元素
    //等价于 auto num(&nums[0])
    //等价于 auto *num = &nums[0]
    //等价于 auto *num = nums
    auto num(nums);
    //===注意:使用decltype不会转换===
    decltype(nums) num1 = {1,2,3};
    int *ptr = nullptr;
    //不能赋值
    //num1 = ptr;
    *p = nums;
    //p指向nums[1]
    ++p;
    --p;
    //等价于指向第3个元素,编译器自动优化为sizeof(int)
    p += 2;
    //获取数组最后元素下一个地址
    int *e = &nums[3];
    //for,这里类似迭代器
    for(int *b = nums; b != e; ++b){
        cout << *b << endl;
    }
    //for,根据空间遍历
    for(int i = 0; i < sizeof(nums) / sizeof(int); i++){
        cout << arr[i] << endl;
    }
    //======begin & end=======
    int *begin = begin(nums);
    int *end = end(nums);
    while(begin != end){
        cout << *begin << endl;
        beg++;
    }
    //end - begin也是数组长度
}
void array_vector(){
    vector<int> v1 = {1,2,3};
    vector<int> v2(v1);
    //数组
    int a1[] = {1,2,3};
    vector<int> a2(begin(a1), end(a1));
}

11.多维数组

严格来说,C++语言中没有多维数组,通常所说的多维数组其实是数组的数组。

void multi_init(){
    int a[3][4] = {
        {0,1,2,3},
        {4,5,6,7},
        {8,9,10,11}
    };
    //等价于
    int b[3][4] = {1,2,3,4,5,6,7,8,9,10,11};
    //初始化每个行首元素,剩余默认值
    int c[3][4] = {{0},{1},{2}};
    //仅仅初始化第一行
    int d[3][4] = {1,2,3,4};
    //row是第二个元素的引用,包含4个元素
    int(&row)[4] = a[1];
}
void multi_for(){
    constexpr size_t rowCnt = 3, colCnt = 4;
    int a[rowCnt][colCnt] = {0};
    for(size_t i = 0; i != rowCnt; ++i){
        for(size_t j = 0; j != colCnt; ++j){
            a[i][j] = i * colCnt + j;
        }
    }
    //可以这样
    size_t cnt = 0;
    for(auto &row : a){
        for(auto &col : row){
            col = cnt++;
        }
    }
    //除了最内层的循环外,其他所有循环的控制变量都应该是引用类型
    //否则编译器自动转为指向首元素的指针,转换后内层就不可以了
}
void multi_ptr(){
    int a[3][4];
    //p指向4个整数的数组
    int(*p)[4] = a;
    //p指向a的最后一个数组
    p = &a[2];
    //使用auto,for迭代
    //++p:前缀递增用于外层循环,强调立即更新指针,通常更高效且意图更清晰
    for(auto p = a; p != a + 3; ++p){
        // q指向4个元素数组的第一个元素
        //q++:后缀递增用于内层循环,遵循传统习惯,后缀递增更直观地表示“先使用再更新”
        for(auto p = *p; q != *p + 4; q++){
            cout << *q << ' ';
        }
        cout << endl;
    }
    //等价于
    for(auto p = begin(a); p != end(a); p++){
        for(auto q = begin(*p); q != end(*p); q++){
            cout << *q << ' ';
        }
        cout << endl;
    }
}

12.函数

  • 局部静态对象:某些时候,有必要令局部变量的生命周期贯穿函数调用及之后的时间。可以将局部变量定义成static类型,第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。
  • 指针传参:其实就是复制了当前指针,进行使用。
  • 引用传参:不需要拷贝,直接使用当前对象别名,和值传递类似,只是在函数定义时参数定义为引用类型。
  • 内联函数:内联函数可避免函数调用的开销将函数指定为内联函数(inline),通常就是将它在每个调用点上“内联地”展开 constexpr函数(constexpr function)是指能用于常量表达式的函数。定义constexpr函数的方法与其他函数类似,不过要遵循几项约定:函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句,其实就是编译器把函数在调用的地方复制了一次。
//使用引用传参返回额外信息
string::size_type find_char(const string &s, char c, string::size_type &occurs){
    //第一次出现的位置
    auto ret = s.size();
    //设置表现出现次数的值
    occurs = 0;
    for(decltype(ret) i = 0; i != s.size(); ++i){
        if(s[i] == c){
            if(ret == s.size()){
                //记录c第一次出现的位置
                ret = i;
            }
            //次数加1
            ++occurs;
        }
    }
    return ret;
}
//返回值为数组的指针或引用,由于数组不能被拷贝u
using arr = int[10];
//返回一个指向10个整数数组的指针
arr *func(int);
//等价于
//Type (*func(params))[dimension]
//func 返回的是一个可以用来访问一个 int 数组(大小为 10)的指针。
int (*func(int))[10];
//等价于,尾置类型
auto func(int i) -> int (*)[10];
//===========================================
//const_cast用法
const string &shorterString(const string &s1, const string &s2)
{
    return s1.size() <= s2.size() ? s1 : s2;
}

string &shorterString(string &s1, string &s2)
{
    auto &r = shorterString(const_cast<const string &>(s1), const_cast<const string &>(s2));
    return const_cast<string &>(r);
}
//========================================
//函数指针
using F = int(int *, int);
using PF = int (*)(int *, int);
//都是函数指针
F *f1(int);
PF f2(int);
int (*f3(int))(int *, int);
//下方是使用范例
PF test1 = &test;
PF test2 = test;
F *test3 = test;
test1(nullptr, nullptr);
test2(nullptr, nullptr);
(*test2)(nullptr, nullptr);
test3(nullptr, nullptr);
最后修改于:2025年01月22日 09:21

添加新评论