C++易忘知识点整理

char类型

C++对字符使用单引号,对字符串使用双引号。字符用单引号括起,表示字符的数值编码。

int main(void) {
    using namespace std;
    char ch = 'M';       // 将 M 的 ASCII 码分配给 ch
    int i = ch;          // 将相同的代码存储在 int 中
    cout << "The ASCII code for " << ch << " is " << i << endl;
    cout << "Add one to the character code:" << endl;
    ch = ch + 1;          // 更改 ch 中的字符代码
    i = ch;               // 在 i 中保存新的字符代码
    //输出时,cout将值78转换为所显示的字符N;
    cout << "The ASCII code for " << ch << " is " << i << endl;
    // using the cout.put() member function to display a char
    cout << "Displaying char ch using cout.put(ch): ";
    cout.put(ch);
    // using cout.put() to display a char constant
    cout.put('!');
    cout << endl << "Done" << endl;
    return 0;
}
/*输出结果
The ASCII code for M is 77
Add one to the character code:
The ASCII code for N is 78
Displaying char ch using cout.put(ch): N!
Done
*/

数组

声明数组的通用格式:typeName arrayName[arraySize]
注意:数组最后一个元素的索引比数组长度小1.如下图
在这里插入图片描述
初始化方式:

int cards[4] = {3,4,5,6,78};
int cards[4] {3,4,5,6,78}; //c++ 11
int cards[4];

上面几种方式都可以。

C-风格字符串

C-风格字符串具有一种特殊的性质: 以空字符 (null character) 结尾,空字符被写作\0, 其 ASCII 码为0, 用来标记字符串的结尾。

char dog[8]={ 'b', 'e', '矿, 'U I I '文, I', 'I', 'I'};// not a string
char cat[8]={'f'' 'a', 't'' 'e'' 's'' 's', 'a','\0'} ;//a string

字符串常量(字符串字面值)

char bird[ll] = "Mr. Cheeps"; 
char fish[] = "Bubbles"; 

在确定存储宇符串所需的最短数组时, 别忘了将结尾的空宇符计算在内。
在这里插入图片描述

数组中使用字符串

int main(void) {
    using namespace std;
    const int Size = 15;
    char name1[Size];               // 空数组
    char name2[Size] = "C++owboy";  // 初始化数组
    cout << "Howdy! I'm " << name2;
    cout << "! What's your name?\n";
    cin >> name1;
    cout << "Well, " << name1 << ", your name has ";
    //strlen返回的是存储在数组中的字符串的长度, 而不是数组本身的长度。
    //另外, strlen()只计算可见的字符, 而不把空字符计算在内.
    cout << strlen(name1) << " letters and is stored\n";
    //计算大小
    cout << "in an array of " << sizeof(name1) << " bytes.\n";
    cout << "Your initial is " << name1[0] << ".\n";
    name2[3] = '\0';                // set to null character
    cout << "Here are the first 3 characters of my name: ";
    cout << name2 << endl;
    return 0;
}
/*打印
Howdy! I'm C++owboy! What's your name?
kim
Well, kim, your name has 3 letters and is stored
in an array of 15 bytes.
Your initial is k.
Here are the first 3 characters of my name: C++
*/

上面程序使用‘\0’截断了数组,具体原理如下图:
在这里插入图片描述

使用new创建动态数组

int * psome = new int[10];
//创建一个包含10个int元素的数组
//new运算符返回第一个元素的地址
//注意:使用new创建的数组,需要delete[]psome

怎么访问数组的元素?
只要把指针当作数组名使用(c++将数组名解释为地址)即可。对于第一个元素,可以使用psome[0],第二个元素使用psome[1],依此类推。
指针和数组基本等价的原因在于指针算术和C++内部处理数组的方式
例如:

将整数变量加1后,其值将增加1;但将指针变量加1后, 增加的量等千它指向的类型
的字节数。 将指向double的指针加1后,如果系统对double使用 8个字节存储, 则数值将增加 8;将指向
short的指针加1后, 如果系统对sho rt 使用2 个字节存储, 则指针值将增加2。

int main(void) {
    using namespace std;
    double wages[3] = {10000.0, 20000.0, 30000.0};
    short stacks[3] = {3, 2, 1};
    // 这里有两种获取数组地址的方法
    double *pw = wages;     // 数组名 = 地址
    short *ps = &stacks[0]; // 或使用地址运算符
    //带数组元素
    cout << "pw = " << pw << ", *pw = " << *pw << endl;
    pw = pw + 1;
    cout << "add 1 to the pw pointer:\n";
    cout << "pw = " << pw << ", *pw = " << *pw << "\n\n";
    cout << "ps = " << ps << ", *ps = " << *ps << endl;
    ps = ps + 1;
    cout << "add 1 to the ps pointer:\n";
    cout << "ps = " << ps << ", *ps = " << *ps << "\n\n";
    cout << "access two elements with array notation\n";
    cout << "stacks[0] = " << stacks[0]
         << ", stacks[1] = " << stacks[1] << endl;
    cout << "access two elements with pointer notation\n";
    cout << "*stacks = " << *stacks
         << ", *(stacks + 1) =  " << *(stacks + 1) << endl;
    cout << sizeof(wages) << " = size of wages array\n";
    cout << sizeof(pw) << " = size of pw pointer\n";
    return 0;
}
/*output
pw = 0x71601ff880, *pw = 10000
add 1 to the pw pointer:
pw = 0x71601ff888, *pw = 20000

ps = 0x71601ff87a, *ps = 3
add 1 to the ps pointer:
ps = 0x71601ff87c, *ps = 2

access two elements with array notation
stacks[0] = 3, stacks[1] = 2
access two elements with pointer notation
*stacks = 3, *(stacks + 1) =  2
24 = size of wages array
8 = size of pw pointer
*/

在这里插入图片描述

指针和字符串

直接看个例子:

int main(void) {
    using namespace std;
    char animal[20] = "bear";
    //将char指针初始化为指向一个字符串,“wren”实际表示的是字符串的地址
    //因此这条语句将“wren”的地址赋值给了bird指针
    const char *bird = "wren";
    char *ps;                  // 未初始化

    cout << animal << " and ";
    cout << bird << "\n";
    // cout << ps << "\n";      //may display garbage, may cause a crash

    cout << "Enter a kind of animal: ";
    cin >> animal;              // ok if input < 20 chars
    // cin >> ps; Too horrible a blunder to try; ps doesn't
    //            point to allocated space
    //这两个指针指向用一块内存和字符串
    ps = animal;                // 注意将animal赋给ps并不会复制字符串,只是复制地址。
    cout << ps << "!\n";       // ok, same as using animal
    cout << "Before using strcpy():\n";
    //因为是char*类型。所以需要强转为指针的地址类型
    cout << animal << " at " << (int *) animal << endl;
    cout << ps << " at " << (int *) ps << endl;
    //创建新的内存空间
    ps = new char[strlen(animal) + 1];  // get new storage
    //第一个参数是目标地址,第二个参数是要复制字符串的地址
    strcpy(ps, animal);         // 将字符串复制到新存储
    cout << "After using strcpy():\n";
    cout << animal << " at " << (int *) animal << endl;
    cout << ps << " at " << (int *) ps << endl;
    delete[] ps;
    // cin.get();
    // cin.get();
    return 0;
}
/*output
bear and wren
Enter a kind of animal: dog
dog!
Before using strcpy():
dog at 0xad4c9ffd40
dog at 0xad4c9ffd40
After using strcpy():
dog at 0xad4c9ffd40
dog at 0x28c890018d0
*/

C-风格字符串比较

C++将C-风格字符串视为地址,所以使用关系运算符比较,无法得到满意的结果。
需要使用strcmp()函数来比较。
1.该函数接受两个字符串地址作为参数,这意味着参数可以是指针,字符串常量或者是字符数组名。
2.如果第一个字符串按字母顺序排在第二个字符串之前, 则strcmp()将返回一个负数值
3.如果第一个字符串按字母顺序排在第 二个字符串之后, 则strcpm()将返回一个正数值

使用关系运算符可以比较字符,因为字符实际上是整数。例如:

for (ch = 'a'; ch <= 'z'; ch++)
	cout << ch;

例如,比较单词:

int main(void) {
    using namespace std;
    char word[5] = "?ate";
    for (char ch = 'a'; strcmp(word, "mate"); ch++) {
        cout << word << endl;
        word[0] = ch;
    }
    cout << "After loop ends, word is 11" << word << endl;
    return 0;
}
/*
?ate
aate
bate
cate
date
eate
fate
gate
hate
iate
jate
kate
late
After loop ends, word is 11mate
*/

7.比较string类字符串

int main() {
    using namespace std;
    string word = "?ate";

    for (char ch = 'a'; word != "mate"; ch++) {
        cout << word << endl;
        word[0] = ch;
    }
    cout << "After loop ends, word is " << word << endl;
    // cin.get();
    return 0;
}

因为string类重载了!= 所以可以这样比较。

函数使用指针处理数组

1.C++将数组名解释其第一个元素的地址:
cookies == &cookies[0];
2.如果将取地址符&用于数组名时,将返回整个数组的地址。
3.在c++中,当且仅当用于函数头或函数原型中,int * arr和int arr [] 的含义才是相同的。都意味着arr是一个int指针。
4.数组表示法 int arr []提醒用户,arr不仅指向int,还指向int数组的第一个int

请记住这个恒等式:

arr[i] == *(ar + i)
&arr[i] == ar + i
将指针(包括数组名) 加1’实际上是加上了一个与指针指向的类型的长度(以字节为单位)
相等的值。对于遍历数组而言, 使用指针加法和数组下标时等效的。

char *buildstr(char c, int n);     // prototype
int main() {
    using namespace std;
    int times;
    char ch;

    cout << "Enter a character: ";
    cin >> ch;
    cout << "Enter an integer: ";
    cin >> times;
    char *ps = buildstr(ch, times);
    cout << ps << endl;
    delete[] ps;                   // free memory
    ps = buildstr('+', 20);         // reuse pointer
    cout << ps << "-DONE-" << ps << endl;
    delete[] ps;                   // free memory
    // cin.get();
    // cin.get();
    return 0;
}

// builds string made of n c characters
char *buildstr(char c, int n) {
    char *pstr = new char[n + 1];
    pstr[n] = '\0';         // terminate string
    while (n-- > 0)
        pstr[n] = c;        // fill rest of string
    return pstr;
}
/*output
Enter a character: z
Enter an integer: 10
zzzzzzzzzz
++++++++++++++++++++-DONE-++++++++++++++++++++
*/

函数指针基础使用

函数也有地址。函数的地址是存储其机器语言代码的内存的开始地址。
可以编写将另一个函数的地址作为参数的函数。 这样第一个函数将能够找到第二个函数, 并运行它。 与直接调用另一个函数相比,这种方法很笨拙, 但它允许在不同的时间传递不同函数的地址, 这意味着可以在不同的时间使用不同的函数。

1.获取函数的地址
使用函数名即可。(后面没有参数)例如:think()是一个函数,则think就是该函数的地址。
2.声明函数指针
我们在声明指向某种数据类型的指针时,必须指定指针指向的类型。
声明指向函数的指针时,也必须指定指针指向的函数类型。这意味着声明应指定函数的返回类型以及函数的特征标(参数列表)。也就是说, 声明应像函数原型那样指出有关函数的信息

double pam(int);
double (*pf)(int);//通常,要声明指向特定类型的函数的指针, 可以首先编写这种函数的原型, 然后用(*pf)替换 函数名。 这样pf就是这类函数的指针。
pf = pam; //注意, pam()的特征标和返回类型必须与pf 相同
double x = pam(4);
double y = (*pf)(5);
//-------------------------------
double y2 = pf(5);
//上面使用pf和(*pf)等价。
//一种认为pf时函数指针,而*pf是函数,因此将(*pf)()用作函数调用。
//一种认为函数名是指向该函数的指针,指向函数的指针的行为应该和函数名相似。

怎么理解?
为了提供正确的运算符优先级,必须在声明中使用括号将pf括起。
括号的优先级比 * 运算符高,因此
pf(int)意味着pf()是一个返回指针的函数,而(*pf)(int)意味着pf 是一个指向函数的指针。

typedef关键字

C++为类型建立别名的方式有两种。
一种是预处理器:
#define BYTE char// preprocessor replaces BYTE with char
这样, 预处理器将在编译程序时用 char 替换所有的 BYTE,从而使 BYTE 成为 char 的别名。

笫二种方法是使用 C++(和 C) 的关键字 typedef 来创建别名。
例如要将 byte 作为 char 的别名,可以这样做:
typedef char byte; / / makes byte an alias for char
下面是通用格式:
typedef typeName aliasName;
换句话说, 如果要将 aliasName 作为某种类型的别名, 可以声明 aliasName, 如同将 aliasName 声明为 这种类型的变量那样, 然后在声明的前面加上关键宇 typedef。例如,要让 byte_pointer 成为 char 指针的别名, 可将 byte_pointer 声明为 char 指针, 然后在前面加上 typedef:
typedef char* byte_pointer;// pointer to char type
注意:某些场景下使用#define,不适用。例如:

#define FLOAT_POINTER float *
FLOAT_POINTER pa,pb;//只有pa是float指针变量,pb只是float类型

上面代码使用typedef方法不会有这样的问题。typedef不会创建新类型,而只是为已有的类型建立一个新的名称。

使用typedef简化函数指针

typedef double const double *(*p_fun)(const double *,int);
p_fun p1 = 	f1;//p1 points to the f1() function

虚函数的工作原理

使用virtual的原因是:希望同一个方法在派生类和基类中的行为不同。

其中有2种机制来实现:
1.在派生类中重新定义基类的方法
2.使用虚方法

通常, 编译器处理虚函数的方法是: 给每个对象添加一个隐藏成员。 隐藏成员中保存了一个指向函数
地址数组的指针。这种数组称为虚函数表 (virtual function table, vtbl)。虚函数表中存储了为类对象进行声
明的虚函数的地址。

例如,
1.基类对象包含一个指针, 该指针指向基类中所有虚函数的地址表
2.派生类对象将包含一个指向独立地址表的指针。
3.如果派生类提供了虚函数的新定义, 该虚函数表将保存新函数的地址;
4.如果派生类没有重新定义虚函数, 该 vtbl 将保存函数原始版本的地址。
5.如果派生类定义了新的虚函数,则该函数的地址也将被添加到vtbl中。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上图解释:
对象A有两个虚函数vfunc1() 和vfunc2(),对象B复写了vfunc1(),并且有自己的func2() 对象C 也覆写了vfunc1()。
总结一下:
1 在基类方法的声明中使用关键字virtual可使该方法在基类以及所有的派生类(包括从派生类派生 出来的类) 中是虚的。
2 如果使用指向对象的引用指针来调用虚方法, 程序将使用为对象类型定义的方法, 而不使用为引用或指针类型定义的方法。 这称为动态联编或晚期联编。 这种行为非常重要, 因为这样基类指 针或引用可以指向派生类对象。
3 如果定义的类将被用作基类, 则应将那些要在派生类中重新定义的类方法声明为虚的。
4 如果派生类没有重新定义函数, 将使用该函数的基类版本。 如果派生类位于派生链中, 则将使用最新的虚函数版本, 例外的情况是基类版本是隐藏的。

const使用

在这里插入图片描述
在这里插入图片描述

decltype

decltype(f())sum = x;//sum的类型就是函数f的返回类型
它的作用是选择并返回操作数的数据类型。在这个过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
需要注意以下几点:
1.如果decltype使用的表达式是一个变量,则decltype返回该变量的类型。(包括顶层const和引用在内。)

const int ci= 0, &cj = Cl;
decltype(ci)x = 0;//x的类型是const int
decltype(cj)y =x;//y的类型是const int&,y绑定到变量x
decltype(cj)z;//错误z是一个引用,必须初始化

2.如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型。
3.有些表达式将向decltype返回一个引用类型。一般来说,意味着该表达式的结果对象能作为一条赋值语句的左值。

int i= 42int* p  = &i;
int &r = i;
//r是一个引用,因此decltype(r)的结果是引用类型。r+0是一个具体的值
decltype(r+0)b;//加法的结果是int 因此b是一个为初始化的int.
//表达式的操作时解引用,则decltype将得到引用类型。
decltype(*p)c;//错误的!!!

解引用指针可以得到指针所指的对象。而且还能给这个对象赋值。

4.decltype ((variable))(注意是双层括号)的结果永远是引用,而 decltype(variable)结果只有当variable本身就是一个引用时才是引用。

#include <iostream>
#include <type_traits>
 
struct A { double x; };
const A* a;
 
decltype(a->x) y;       // y 的类型是 double(其声明类型)
decltype((a->x)) z = y; // z 的类型是 const double&(左值表达式)
 
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) // 返回类型依赖于模板形参
{                                     // C++14 开始可以推导返回类型
    return t+u;
}
 
int main() 
{
    int i = 33;
    decltype(i) j = i * 2;
 
    std::cout << "i = " << i << ", "
              << "j = " << j << '\n';
 
    std::cout << "i 和 j 的类型相同吗?"
              << (std::is_same_v<decltype(i), decltype(j)> ? "相同" : "不同") << '\n';
 
    auto f = [](int a, int b) -> int
    {
        return a * b;
    };
 
    decltype(f) g = f; // lambda 的类型是独有且无名的
    i = f(2, 2);
    j = g(3, 3);
 
    std::cout << "i = " << i << ", "
              << "j = " << j << '\n';
}
/*output
i = 33, j = 66
i 和 j 的类型相同吗?相同
i = 4, j = 9
*/

模板别名using

用法:
对命名空间的 using 指令及对命名空间成员的 using 声明.

void f();
 
namespace A
{
    void g();
}
 
namespace X
{
    using ::f;        // 全局 f 现在作为 ::X::f 可见
    using A::g;       // A::g 现在作为 ::X::g 可见
    using A::g, A::g; //(C++17)OK:命名空间作用域允许双重声明
}
 
void h()
{
    X::f(); // 调用 ::f
    X::g(); // 调用 A::g
}

对类成员的 using 声明.

#include <iostream>
 
// 基类 B
struct B
{
    virtual void f(int) { std::cout << "B::f\n"; }
    void g(char)        { std::cout << "B::g(char)\n"; }
    void h(int)         { std::cout << "B::h\n"; }
protected:
    int m; // B::m 是受保护的
    typedef int value_type;
};
 
// 派生类 D
struct D : B
{
    using B::m; // D::m 是公开的
    using B::value_type; // D::value_type 是公开的
 
    // using 的非必要使用场合:
    // B::f 已经公开,它作为 D::f 公开可用,
    // 并且下面的 D::f 和上面的 B::f 有相同的签名。
    using B::f;
    void f(int) { std::cout << "D::f\n"; } // D::f(int) **覆盖** B::f(int)
 
    // using 的必要使用场合:
    // B::g(char) 被 D::g(int) 隐藏,
    // 除非它在这里通过 using B::g 显式暴露为 D::g(char)。 
    // 如果不用 using B::g,将 char 传递给 D::g(char) 实际上调用的是下面定义的 D::g(int),
    // 因为后者隐藏了 B::g(char),并且 char 形参会因此隐式转型到 int。
    using B::g; // 将 B::g(char) 作为 D::g(char) 暴露,
                // 后者与下面定义的 D::g(int) 完全是不同的函数。
    void g(int) { std::cout << "D::g(int)\n"; } // g(int) 与 g(char) 均作为 D 的成员可见
 
    // using 的非必要使用场合:
    // B::h 已经公开,它作为 D::h 公开可用,
    // 并且下面的 D::h 和上面的 B::h 有相同的签名。
    using B::h;
    void h(int) { std::cout << "D::h\n"; } // D::h(int) **隐藏** B::h(int)
};
 
int main()
{
    D d;
    B& b = d;
 
//  b.m = 2;  // 错误:B::m 受保护
    d.m = 1;  // 受保护的 B::m 可以作为公开的 D::m 访问
 
    b.f(1);   // 调用派生类的 f()
    d.f(1);   // 调用派生类的 f()
    std::cout << "----------\n";
 
    d.g(1);   // 调用派生类的 g(int)
    d.g('a'); // 调用基类的 g(char),它**只是因为**
              // 派生类中用到了 using B::g; 才会暴露
    std::cout << "----------\n";
 
    b.h(1);   // 调用基类的 h()
    d.h(1);   // 调用派生类的 h()
}
/*output
D::f
D::f
----------
D::g(int)
B::g(char)
----------
B::h
D::h
*/

类型别名与别名模板声明 (C++11 起).

#include <string>
#include <ios>
#include <type_traits>
 
// 类型别名,等同于
// typedef std::ios_base::fmtflags flags;
using flags = std::ios_base::fmtflags;
// 名字 'flags' 现在指代类型:
flags fl = std::ios_base::dec;
 
// 类型别名,等同于
// typedef void (*func)(int, int);
using func = void (*) (int, int);
// 名字 'func' 现在指代函数指针:
void example(int, int) {}
func f = example;
 
// 别名模板
template<class T>
using ptr = T*; 
// 名字 'ptr<T>' 现在是指向 T 的指针的别名
ptr<int> x;
 
// 用于隐藏模板形参的别名模版
template<class CharT>
using mystring = std::basic_string<CharT, std::char_traits<CharT>>;
mystring<char> str;
 
// 别名模板可引入成员 typedef 名
template<typename T>
struct Container { using value_type = T; };
// 可用于泛型编程
template<typename ContainerType>
void g(const ContainerType& c) { typename ContainerType::value_type n; }
 
// 用于简化 std::enable_if 语法的类型别名
template<typename T>
using Invoke = typename T::type;
template<typename Condition>
using EnableIf = Invoke<std::enable_if<Condition::value>>;
template<typename T, typename = EnableIf<std::is_polymorphic<T>>>
int fpoly_only(T t) { return 1; }
 
struct S { virtual ~S() {} };
 
int main() 
{
    Container<int> c;
    g(c); // Container::value_type 将在此函数中是 int
//  fpoly_only(c); // 错误:被 enable_if 禁止
    S s;
    fpoly_only(s); // OK:被 enable_if 允许
}

explicit关键字

指定构造函数或转换函数 (C++11 起)或推导指引 (C++17 起)为显式,即它不能用于隐式转换和复制初始化。
声明时不带函数说明符 explicit 的拥有单个无默认值形参的 (C++11 前)构造函数被称作转换构造函数。

struct A
{
    A(int) { }      // 转换构造函数
    A(int, int) { } // 转换构造函数(C++11)
    operator bool() const { return true; }
};
 
struct B
{
    explicit B(int) { }
    explicit B(int, int) { }
    explicit operator bool() const { return true; }
};
 
int main()
{
    A a1 = 1;      // OK:复制初始化选择 A::A(int)
    A a2(2);       // OK:直接初始化选择 A::A(int)
    A a3 {4, 5};   // OK:直接列表初始化选择 A::A(int, int)
    A a4 = {4, 5}; // OK:复制列表初始化选择 A::A(int, int)
    A a5 = (A)1;   // OK:显式转型进行 static_cast
    if (a1) ;      // OK:A::operator bool()
    bool na1 = a1; // OK:复制初始化选择 A::operator bool()
    bool na2 = static_cast<bool>(a1); // OK:static_cast 进行直接初始化
 
//  B b1 = 1;      // 错误:复制初始化不考虑 B::B(int)
    B b2(2);       // OK:直接初始化选择 B::B(int)
    B b3 {4, 5};   // OK:直接列表初始化选择 B::B(int, int)
//  B b4 = {4, 5}; // 错误:复制列表初始化不考虑 B::B(int,int)
    B b5 = (B)1;   // OK:显式转型进行 static_cast
    if (b2) ;      // OK:B::operator bool()
//  bool nb1 = b2; // 错误:复制初始化不考虑 B::operator bool()
    bool nb2 = static_cast<bool>(b2); // OK:static_cast 进行直接初始化
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值