Zexian Li

C++小记

2020-05-08 · 13 min read
C++

迫于刷题,简单刷了一遍C++入门教程,对一些基础知识作简单记录。

1. 基本常识

#include <iostream>
using namespace std;
int main(void)
{
    int a;
    cin >> a;
    if (a > 0){
        for(int i = a; i > 0; i--)
            cout << "hello" << endl;
    }
    else if (a < 0)
        cout << a << " less than 0" << endl;
    else{
        cout << a << " is 0";
        cout << endl;
    }
    return 0;
}

2. 定义数组

int nums1[2][5] = {{1, 2, 3}, {4, 5, 6, 7}}; // Fill empty with 0.
int nums2[][3] = {{1, 2, 3}, {4, 5, 6}}; 
float nums3[] = {1.23, 2.34, 3.45};
``
在C++中,使用如下语句会报错:
```C++
int n1 = text1.size();
int n2 = text2.size();
int dp[n1][n2] = {{0}, {0}}; // all to zero

原因是C允许使用变量来定义数组长度,却不允许同时进行初始化赋值,需要在之后赋值。
C++利用指针进行动态数组的定义,需注意动态数组需及时释放以防止内存泄漏。下例是用指针和vector进行一维/二维数组创建的过程。

// dynamic 1-d array + initialization
int *a = new int[10](); 
cout << a[2] << endl;
delete [] a;

// create a dynamic 2-d array whose size is 3*2
// (method 1) - preferred
int (*value)[2] = new int[3][2]; 
cout << value[1][1] << endl;
delete [] value;

// (method 2) - preferred
vector<vector<int>> value1(3);
for(int i = 0; i < value1.size(); i++)
    value1[i].resize(2);

// (method 3) - not preferred
int **value2 = new int*[3];
for(int i = 0; i < 3; i++)
    value2[i] = new int[2];
for(int i = 0; i < 3; i++)
    delete []value2[i];

3. 字符串

在C语言中我们如下在非初始化时对字符串变量赋值,但不可以进行字符串变量的互相赋值:

#include <string.h>

char str1[3][15];
//str1[0] = "abcde"; //False
strcpy(str1[0], "abcde");

在C++中,对字符串的处理变得更加自由:

char str1[] = "abc";
char str2[20];
cin.getline(str2, 10, '\n'); // string, max length, end char.
// char str3 = str1; // False
string str3 = str1;
char ch[] = ",";
string str1;
cin >> str1; //abc
string str2("def");
string str3 = "ghi";
string full_str = str1 + str2 + str3;
cout << full_str << endl;//abcdefghi

对于C++字符串更多的声明方式和操作函数详见博客

4. 指针

通过&来得到变量的地址,例如&a即为a的地址。通过*来读取声明过变量地址的变量的值,例如*(&a)即为a的值。我们常常使用的指针变量,其变量值只是一个地址。
如下为指针使用简例:

int Value = 10;
// First implementation 
// pointer variable'piVal' contains the address of 'Value'.
//      piVal = &Value, *piVal = Value
int *piVal = &Value; 
// pointer variable'piVal2' contains the address of 'piVal'.
//      piVal2 = &piVal, *piVal2 = piVal
int **piVal2 = &piVal; 
*piVal = 20;
// Second implementation
double Value = 1.23;
double* piVal = 0; // 0 means NULL but not zero, both 'int* a' and 'int *a' are legal. 
piVal = &Value;

如下显示两种错误的声明方式,会造成指针变量指向不合法地址:

// WRONG CASE ! Pointer should point to a variable, but not a value.
int* piVal = 10;

int* piVal;
*piVal = 10;
  1. 指针与数组的关系
    指针和数组也有联系,n维数组可以当成对n维指针的应用,数组名即为n重指针变量。如下为常见的指针赋值方式:
int a[3][4];
int *p = a[0]; //method 1
int *p = &a[0][0]; // method 2

定义二维数组a[3][4],a代表&a[0]*(a+1)a[1]=&a[1][0],不难得到&a[i][j] = *(a+i)+ja[i][j] = *(*(a+i)+j)
在实际应用中可以使用指针数组,指针数组中的每个元素都是一个指针变量,指针变量的值指向其他变量的地址。如下例:

char *name[3] = {"dahan", "minguo", "wansui}; // name[1] = &("minguo")
  1. 指针与字符串的关系
    既然数组可以,C++的字符串同样可以使用指针来进行声明和操作,如下例:
char *p = "MinGuGu";// p = &(char), *p = "M", *(p+1) = "i"
cout << p << endl; // MinGuGu
cout << (int*)p << endl; // memory address

注意指针变量(如上例p)的值是可以改变的,eg将其地址加1从而指向&a[1]等。但以char a[10] = "GuGu"方式定义时,a为指针常数,不可改变其值。
3. 指针与函数的关系
C++中可以将函数指针作为另一个函数的参数,以高效地改变调用的函数。与一般的函数指针常声明为全局/局部变量不同,参数型函数指针直接声明于函数的参数行中,如下例所示:

int add(int a, int b) {return a+b;}
int sub(int a, int b) {return a-b;}
int Math(int a, int b, int (*pfunc)(int, int)) {return pfunc(a, b);}

result = Math(a, b, add)//pfunc = add = &(fun(add))

将上例再演变一下,可以成为函数指针数组。如下例所示:

int (*pfunc[])(int, int) = {add, sub};
int a =1, b = 2;
cout << pfunc[0](a, b) <<  endl; //pfunc[0] = add = &(fun(add))

5.基本函数常识

回顾C++的函数写法,可以参照这个例子:

void my_sum(int a, int b=20); // declaration 1
//void my_sum(int*, int*); // declaration 2
int main(){
    int a, b;
    cin >> a  >> b;
    my_sum(a, b);
    return 0;
}
void my_sum(int a, int b){
    cout << a+b << endl;
}

同样地,数组也可以作为函数的参数,例如下声明void fun1(int a[][2], float b)
同样地,指针也可以作为函数的参数和返回值,例如一个实现将两个字符串相加的函数,其声明可以这样写char* Stract(char*, char*),Stract函数的参数和返回值均为字符串。

6.高级函数知识

  1. 函数指针
    指向函数起始地址的指针变量称为函数指针。此时,函数名称便是一个指针变量,变量存储的值就是函数内容所在内存的起始地址。基于此,可实现动态的函数调用。以下为函数指针的声明示例:
void (*ptr)(void); //ptr为函数指针,函数无返回值,函数无参数
int (*ptr)(int); //ptr为函数指针,函数返回整数值,函数接受整数参数
//int *ptr(int); //ptr为函数名,函数返回整数指针,函数接受整数参数
char* (*ptr)(char); //ptr为函数指针,函数返回字符指针,函数接受字符串参数 

在此基础上的函数指针初始化及赋值方法如下所示:

int fun1();
//declaration 1
int (*pfun)() = fun1; 
//declaration 2
int (*pfun)();
pfun = fun1;
  1. 命令行参数
    当程序需要传递信息时,可以在main()函数中使用argc和argv两个命令行参数。声明方式如下:
    int main(int argc, char *argv[])。(argc和argv只是默认命名,实际可变但大可不必)
    其中,argc数据类型为整数,表示命令行参数的个数,argc的值定大于0,因为至少包括程序本身的名称;argv为不定长度的字符串指针数组,传递的字符串个数视输入具体参数数量而定。
    在命令提示符模式输入helloworld "look at GUGU" look.lovely.GUGU this is a test.,argc的实际值为7,argv的长度为7,同时argv[0] = "helloworld",argv[6] = "test."。
  2. 局部变量
    在特定程序区块内,当局部变量与全局变量名重复而冲突时,局部变量优先,但区块执行结束后,全局变量又恢复到原来的设定值。
  3. 变量等级声明
    C++提供了五种变量等级类型修饰词,包括auto,static,extern,static extern和register。
    加上auto修饰词/无修饰词时默认为自动变量。自动变量必须声明在函数的区块内,程序进入区块才会建立对应的自动变量,离开此范围时变量内存(变量值+内存地址)被释放;
    在函数或程序段中声明成static的变量为静态局部变量,也可以理解为函数内定义的不跨程序文件的全局变量。静态局部变量在对应区块执行完毕后不会清除内存,内存在程序全部结束时才释放。与一般变量不同的是,静态局部变量在未设置初值时初始化为0;
    加上extern修饰词/在函数或程序区块外声明的变量为外部变量,也可以理解为函数外定义的跨程序文件的全局变量。在函数内使用extern修饰变量时不会实际分配内存,但在函数外部需要有一个同名变量存在时才会实际分配内存。外部变量在未设置初值时初始化为0;
    使用static修饰词声明的外部变量称为静态外部变量,可以理解为函数外定义的不跨程序文件的全局变量。对应变量的内存在程序全部结束后才会释放;
    使用register修饰词声明的变量称为寄存器变量,使用CPU的寄存器来存储变量。当变量所声明的程序区块与函数结束时才释放寄存器变量。
    外部变量的具体使用方法可参照下例:在a.cpp中声明全局变量x,在b.cpp中要使用变量x,故需要用extern修饰词在b.cpp中声明该变量,表示将全局变量引用至另一个文件中:
// a.cpp
#include<iostream>
using namespace std;

#include "b.cpp"

int x;
int main()
{
	fun1();
	cout << x << endl; // 886
	return 0;
}
//b.cpp
#include <iostream>

extern int x;

void fun1(){
	x = 886;
}
  1. 函数重载
    函数重载是C++的新特性,即同一个函数名称可以用来定义多个函数主体,程序在调用该函数名时C++会根据传递的参数个数与数据类型来决定实际调用的函数。如下例函数声明所示:
char* get_data(char*);
int get_data(int);
float get_data(float);
double get_data(double);
  1. 内联函数
    inline声明的函数为内联函数,为避免重复调用浪费时间,编译器在遇到对应函数时会直接将内部代码替换成内联函数的代码。可以类比函数层面的#define宏定义理解。

7.预处理指令

  1. define
    常以类#define PI 3.14#define STR "GUGU"#define HELLOWORLD cout<<"helloworld"<<endl;#define FUN func1()的形式进行常量、字符串、程序语句、函数名的宏定义(宏定义的本质是在编译时直接替换),以#undef PI形式取消宏定义。
    对于简单的函数,可以使用宏函数,类似以#define CAL_S(l, h) ((l)*(h))/2.0方式定义三角形面积计算函数。
  2. include
    使用#include <filename>加载默认系统目录下的文件。
    使用#include "filename"优先加载当前程序工作目录下文件(也可以使用此语句加载系统目录的文件)。
  3. C++标准预处理宏
    C++内置了诸如__LINE____FILE____DATE____TIME____STDC__等宏辅助程序编写。
  4. 条件编译指令
    可以设置条件以使用条件编译方法辅助程序编写,例如#if#else#elif#endif#ifdef#ifndef等。

8.自定义数据类型

C++中包括结构(struct)、枚举(enum)、联合(union)与类型定义()typedef这四种自定义数据类型。

  1. 结构
    定义一个结构时并不是声明一个变量,而是定义一种数据类型。结构的使用简例如下所示:
struct Student
{
    char name[20];
    int score;
    int *p;
};
Student Barry = {"Barry", 100};
cout << Barry.name << endl; 

todo

🎅

Bad decisions make good stories.