首页 > 其他 > 详细

【CPP】CPP 基础

时间:2020-12-17 15:35:53      阅读:25      评论:0      收藏:0      [点我收藏+]

CPP 基础

C 语言基础

C 语言关键字

char, short, int, long, float, double, struct, union, enum, void, const, signed, unsigned, sizeof, typedef, auto, static, register, extern, if, else, switch, case, default, for, while, do, break, continue, return

基本数据类型

  • char, short, int, long, long long, float, double, void, struct, union, enum等。
  • 在基本数据类型前加unsigned表示无符号类型,最高位不作为符号位,而是数值位。
  • 整数默认是int,小数默认是double。所以定义long类型时最好在数字末尾加l, L,定义float类型最好在数字末尾加f, F
基本数据类型 字节数 范围
char 1 -128~127
short 2 -32768~32767
int 2 或 4 -231~231-1
long 4 -231~231-1
long long 8 -264~264-1
float 4 --
double 8 --

变量的存储类型

变量除了有数据类型,还有另外一个重要属性,存储类型。数据类型决定了变量的大小,而存储类型决定了变量的生命周期和可见性。

C 语言中,变量的存储类型有 4 种,auto, static, register, extern,一般使用的只有前两个。

  • auto,默认存储类型。auto 只能用在函数内,修饰局部变量。
  • static,全局变量默认以 static 修饰。当修饰局部变量时,必须且只能被初始化一次
  • register,用于定义存储在寄存器中而不是 RAM 中的局部变量,用于运算要求高的变量。
  • extern,用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。

算数运算符

  • +, -, *, /, %, ++, --, <, <=, >, >=, ==, !=, &&, ||, !, &, |, ~, ^, >>, <<, +=, sizeof, ., [], *, (), ?:, 强制类型转换
  • a^b=c, 则a^c=b, b^c=a,这一规律广泛运用于密码学。
  • a>>n,相当于a/(2^n)
    • 有符号数,右移时最高位补符号位。
    • 无符号数,右移时最高位补 0。
  • a<<n,相当于a*2^n,最低位直接补 0。

数组与字符串

  • int a[10]={1, 2, 3},前 4 个数分别为 1, 2, 3,后面的数均初始化为 0。
  • 字符串的末尾必须是\0,占一个字节。

指针

变量都存放在从某个内存地址开始连续的若干个字节中。

  • 开始的内存地址:指针。
  • 连续的若干字节:指针对应类型的字节长度。
  • int *p = (int *)1000,指针指向内存地址为 1000 的位置,管辖后面连续的 4 个字节。

指针变量的大小

指针变量是一种数据类型,它的大小均为sizeof(T *) = 4,故无论指针变量指向的是何种数据类型,它的大小均为 4 字节。

这是因为当今主流的 CPU 为 32 位,寻址范围是 2^32=4G,所以指针变量只需要 32 位即可。当然对于 64 位 CPU,编译器产生的指针长度也就是sizeof(T *) = 8

主机字节顺序

  • 大端:低地址存放高位数据,这种最为直观。
  • 小端:低地址存放低位数据,在程序和网络中,一般采用的是小端。

例如,将0x1234bacd以不同的方式存储:

地址 大端 小端
0x0000 12 cd
0x0001 34 ba
0x0002 ab 34
0x0003 cd 12
unsigned short a = 0x1234;
char *p = (char *)&a;
for(int i=0;i<2;i++){
    printf("%2x\n", *(p+i));
}

如果short a = 0xf234,在输出首字节的0xf2时,会出现fffffff2而不是f2,猜测可能是由于printf()函数在输出时将char自动转换成了 4 字节的int造成的,因为%x对应的就是整型,整型默认是int

指针运算

  • 同类型的指针可以比较。
  • 同类型的指针可以相减。p2-p1 = (p2-p1)/sizeof(T)
  • 指针可以和整型常量相加,相减。p ± n = p ± n*sizeof(T)
  • 指针可以自增,自减。

指针与动态分配内存

  • 分配内存:char *p = (char *)malloc(200 * sizeof(char))
  • 释放内存:free(p)

指针类型

  • 空指针,也称野指针,未指向任何地址的指针。p = NULL
  • void 指针,指向 void 类型的指针,可以强制转换为任意类型指针。
  • 指向指针的指针,int **p
  • 数组和字符串与指针,指向数组和字符串首地址的指针。对于二维数组,还区分行指针和列指针,行指针也就是指向指针的指针。
  • 指向函数的指针,该类指针指向某一类型的函数。
  • 常量指针,指针的指向可以改,但是指向的值不可以改。const int *p = &a
  • 指针常量,指针的指向不可以改,但是指向的值可以改。int * const p = &a
int add(int a, int b)
{
    return a+b;
}

int main()
{
    int (*p)(int, int); // 定义了一个指向某一类型的函数指针p
    p = add;
    cout << p(1, 2) << endl;
}

结构体

定义结构体

stuct student
{
    string name;
    int age;
};

使用结构体

// 初始化
student s1;
student s2 = {"Tom", 18};

cout << s2.name << endl; // 变量

student s3 = {
    name : "Alice",
    age : 16,
};
student *p = &s3;

cout << p->name << endl; // 指针

联合体

定义联合体

union data
{
    int a;
    float b;
    char c[20];
}

联合体的大小以最大的类型为主,也就是说上面定义的data联合体的大小为 20 字节。

使用联合体

联合体在同一时间只使用一个变量,因为他们共用一块最大的内存。

union data data1;

data1.i = 10;
data1.f = 220.5; // 此时会覆盖掉数据 10
strcpy( data1.str, "C Programming"); // 此时会覆盖掉数据 220.5

printf( "data.str : %s\n", data1.str);

枚举类型

// 第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1
enum DAY
{
    SUN, MON, TUE, WED, THU, FRI, SAT
};

// 可以为单独某个值指定数值,后续枚举成员的值仍在这个基础上加 1
// 如下,spring = 0, summer = 3, autumn = 4, winter = 5
enum season {spring, summer=3, autumn, winter};

预处理

引用头文件

  • #include <stdio.h>
  • #include "mymath.h"

宏定义与条件编译

#define, #undef, #ifdef, #ifndef, #if, #else, #elif, #endif

带参数宏

#define add(a, b) a+b

头文件的多次引用

如果一个头文件被引用两次,编译器会处理两次头文件的内容,这将产生错误。为了防止这种情况,标准的做法是把文件的整个内容放在条件编译语句中,一般使用#idndef xx来解决这一问题,放在头文件中。

#ifndef _MYMATH_H
#define _MYMATH_H
// xxx
#endif

extern "C"

extern "C"的主要作用就是为了能够正确实现 C++ 代码调用其他 C 语言代码。加上extern "C"后,会指示编译器这部分代码按 C 语言(而不是 C++)的方式进行编译。

由于 C 和 C++ 编译器(对应 gcc 和 g++)对函数的编译处理是不完全相同的,尤其对于 C++ 来说,支持函数的重载,编译后的函数一般是以函数名和形参类型来命名的。例如函数void fun(int, int),编译后的可能是_fun_int_int(不同编译器可能不同,但都采用了类似的机制,用函数名和参数类型来命名编译后的函数名)。而 C 语言没有类似的重载机制,一般是利用函数名来指明编译后的函数名的,对应上面的函数可能会是_fun这样的名字。

// 模块 A 头文件 include/moduleA.h
#idndef _MODULE_A_H
#define _MODULE_A_H

int foo(int x, int y);

#endif

// 模块 A 实现文件 include/moduleA.c
#include "moduleA.h"

int foo(int x, int y)
{
    return x + y;
}

// 主函数 src/main.cpp
#include <bits/stdc++.h>

#include "moduleA.h"

using namespace std;

int main()
{
    printf("%d\n", foo(1, 2));
    return 0;
}

此时,如果模块 A 的实现文件采用 C 编译,而模块 B 采用 C++ 编译,那么在链接阶段,链接器会从模块 A 生成的目标文件 moduleA.obj 中找_foo_int_int这样的符号,显然这是不可能找到的,因为foo()函数被编译成了_foo的符号,因此会出现链接错误

gcc -c moduleA.c && cd ../src
g++ -I ../include main.cpp ../include/moduleA.o

# 报错
/bin/ld: /tmp/cc6vh97T.o: in function `main‘:
main.cpp:(.text+0x13): undefined reference to `foo(int, int)‘
collect2: error: ld returned 1 exit status

如果在主函数文件中,加入extern "C",对引用头文件部分指明用 C 语言编译,即可正确调用。

#include <bits/stdc++.h>

#ifdef __cplusplus
extern "C" {
#endif

#include "moduleA.h" // C 编译

#ifdef __cplusplus
}
#endif

using namespace std;

int main()
{
    printf("%d\n", foo(1, 2));
    return 0;
}

这个功能十分有用处,因为在 C++ 出现以前,很多代码都是 C 语言写的,而且很底层的库也是 C 语言写的,为了更好的支持原来的 C 代码和已经写好的 C 语言库,需要在 C++ 中尽可能的支持 C,而extern "C"就是其中的一个策略。这个功能主要用在下面的情况:

  • C++ 代码调用 C 语言代码
  • 在 C++ 的头文件中使用
  • 在多个人协同开发时,可能有的人比较擅长 C 语言,而有的人擅长 C++,这样的情况下也会有用到。
#ifndef __INCvxWorksh // 防止该头文件被重复引用
#define __INCvxWorksh

#ifdef __cplusplus // 告诉编译器,如果定义了 __cplusplus(即如果是cpp文件),那么这部分代码按 C 语言的格式进行编译,而不是 C++
extern "C"{
#endif

// 这里可以是复合语句
// 也可以是头文件,相当于头文件中的声明都加了 extern "C"
/*...*/

#ifdef __cplusplus
}
#endif

/*...*/

#endif // end of __INCvxWorksh

读写操作

标准输入输出

  • printf(),标准输出到stdout, stderr
  • scanf(),标准输入到stdin
  • int fgetc(FILE *),返回输入的一个字符,EOF时返回-1。
  • int fputc(int c, FILE *),输出一个字符。
  • char *fgets(char *s, int n, FILE *),输入一段文本以回车结束。
  • int fputs(const char *s, FILE *),输出一段文本。
  • sscanf(buf, "%d%d", &a, &b),从数组中读。
  • sprintf(buf, "%d%d", a, b),写入数组。
  • fscanf(FILE *, 其他),从文件读。
  • fprintf(FILE *, 其他),写入文件。

重定向

  • freopen("in.txt", "r", stdin);
  • freopen("out.txt", "w", stdout);

文件读写

打开文件

FILE *fopen(const char * filename, const char * mode);,其中,filename是文件路径的字符串,mode是打开模式,如下:

模式 含义
r 只读
w 覆盖写,若文件不存在则创建
a 追加写,若文件不存在则创建
xb 操作的是二进制
x+ 允许读写
读写文件
  • int fputc( int c, FILE *fp );,返回输出的字符,否则 EOF。
  • int fputs( const char *s, FILE *fp );,成功返回一个非负值,否则 EOF。
  • int fgetc( FILE * fp );,返回读取的字符,否则 EOF。
  • char *fgets( char *buf, int n, FILE *fp );
  • fscanf(FILE *, 其他),从文件读。
  • fprintf(FILE *, 其他),写入文件。
  • size_t fread(void *buf, size_t size_of_elements, size_t number_of_elements, FILE *a_file);,返回已读取字节数。
  • size_t fwrite(const void *buf, size_t size_of_elements, size_t number_of_elements, FILE *a_file);,返回已写入字节数。

  • int fseek(FILE *stream, long offset, int whence);,移动文件指针到指定位置。
    • offset是相对于 whence 的偏移位置,正数往后,负数往前。
    • whence可以是SEEK_SET, SEEK_CUR, SEEK_END,分别是文件头,当前位置,文件尾。
  • int ftell(FILE *stream),返回当前文件指针位置距离文件头的字节数。

通过上面两个函数可以获取一个文件的大小:

FILE *fp = fopen("in.txt", "r");
fseek(fp, 0, SEEK_END);
int size = ftell(fp);
fseek(fp, 0, SEEK_SET);

关闭文件

int fclose( FILE *fp );

时间函数

struct tm {
   int tm_sec;         /* 秒,范围从 0 到 59 */
   int tm_min;         /* 分,范围从 0 到 59 */
   int tm_hour;        /* 小时,范围从 0 到 23 */
   int tm_mday;        /* 一月中的第几天,范围从 1 到 31 */
   int tm_mon;         /* 月,范围从 0 到 11 */
   int tm_year;        /* 自 1900 年起的年数 */
   int tm_wday;        /* 一周中的第几天,范围从 0 到 6 */
   int tm_yday;        /* 一年中的第几天,范围从 0 到 365 */
   int tm_isdst;       /* 夏令时 */
};
  • time_t time(time_t *t),返回自元时间以来经过的秒数,该秒数会同时返回给参数和返回值。
  • struct tm *localtime(const time_t *t),返回当地时区时间。
  • struct tm *gmtime(const time_t *t),返回用标准格林尼治时区(GMT)表示。
  • time_t mktime(stuct tm *time),将结构体时间转化为秒数。
  • double difftime(time_t t2, time_t t1),返回 t2-t1 的差值。
  • size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr),根据 format 中定义的格式化规则,格式化结构 timeptr 表示的时间,并把它存储在 str 中。
  • clock_t clock(void),返回处理器时钟所以用时间,用于得到程序使用时间。

其他

命令行参数

// ./a.ot hahaha
int main(int argc, char *argv[])
{
    printf("可执行程序 %s,参数个数为[%d],运行输出:[%s]\n", argv[0], argc, argv[1]);
    return 0;
}

控制台动态刷新结果

printf("\r%d", count++);
// printf("\b\b%d", count++);

CPP 的基础

定义常量

  • #define PI 3.141592654
  • const double PI = 3.1415926;

CPP 关键字

bool, namespace, using, class, private, public, protected, this, super, template, new, delete, inline, virtual, true, false

引用类型

引用变量是一个别名,引用是一种安全指针的机制。T & a = b;

int a = 5;
int &b = a; // b 为 a 的引用
int swap(int &a, int &b)
{
    int temp = a;
    a = b;
    b = temp;
}

int a = 1, b= 2;
swap(a, b);

C++ 指针与动态分配内存

C++ 程序中的内存分为两个部分:

  • 栈:在函数内部声明的所有变量都将占用栈内存。
  • 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。

很多时候,我们无法提前预知需要多少内存来存储某个定义变量中的特定信息,所需内存的大小需要在运行时才能确定。在 C++ 中,就可以使用 new 运算符为给定类型的变量在运行时分配堆内的内存,它会返回所分配的空间地址。

  • 分配内存:int *p = new int, int *p = new int[10]
  • 释放内存:delete p, delete[] p

形参的默认值

在 CPP 中,函数的形参可以指定默认值,并且拥有默认值的参数必须放在最后面。

若定义的函数有前向引用申明,那么默认参数值必须在声明中定义,在实现的时候不可以再次指定默认值。

函数的重载

函数名相同,函数的参数个数或者参数类型不同,则称之为函数重载。

函数的返回值类型不可以作为判断函数重载的依据。

内联函数

定义函数时在返回类型前面添加关键字inline,在编译时会用在调用函数的位置展开函数体,以减少参数传递,控制转移等的开销。

内联函数体内不能有循环和 switch 等语句,函数体必须简单。

异常处理

try {
    // 保护代码
} catch (ExceptionName e1) {
    // catch 块
    cout << e1.what() << endl;
} catch (ExceptionName e2) {
    // catch 块
} catch (ExceptionName eN) {
    // catch 块
} catch (...) {
    // 处理任何异常
}
try {
    if (str == "int") throw 100;
    else throw "ahhaha";
} catch(const int a) {
    cout << a << endl;
} catch(const string b) {
    cout << b << endl;
} catch( ... ) {
    cout << "other exception" << endl;
}

多线程

#include <iostream>
#include <thread>

using namespace std;

void fun(int num)
{
    while(true){
        cout << num << endl;
    }
}

int main()
{
    thread t1(fun, 1);
    thread t2(fun, 2);

    // t1.join(),主线程会阻塞在这里
    // t1.detach(),线程会后台运行,无需阻塞等待
    // 主线程不会等待子线程结束
    t1.join();
    t2.join();
}

【CPP】CPP 基础

原文:https://www.cnblogs.com/zghong/p/14149140.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!