cndaqiang Web Linux DFT

C++ 学习笔记

2021-03-30
cndaqiang
Cpp
RSS

虽然整理成博客,很浪费时间,不过,每次忘记后,再捡起来,还要好久,还是继续把笔记整理成文章
该文不完全,遇到其他的语法再添进来

参考

C++中文版【王刚 杨】

注意

  • 分号;表示语句结束,必不可少
  • 注释 - 单行注释 //开头 - 继承自C语言/**/之间,单行或多行,不能嵌套
  • 缩进自由,{可以放在函数/while/…等的同行也可以下一行
  • 大小写敏感
  • 变量名一般用小写字母, 用户自定义的类一般大写字母开头, 多个单词组成的,单词首字母大写或下划线隔开
  • 变量在用之前定义/声明就可以,不用像Fortran一样在开头定义
  • 使用标准库中的命令空间中的对象,使用域操作符::,如std::cin
    可以使用using std::cin,之后就可以直接使用cin替换std::cin
  • 引入了#include<iostream>后, 这两个标准库#include<string> #include<cctype>没引入也可以使用相应std中的变量
  • 语句以;结束,可以用空语句. 多加;的问题: while( condition) ;表示循环体是空的
  • 语句块用{}括起来,{}外面不加;,空块{}也表示空语句
  • 在if的{}定义的变量、引用等操作,在{}外无效

头文件

头文件通常包含哪些只能被定义一次的实体,如类、const、constexpr变量等.
头文件一旦改变,相关源文件必须重新编译以获得更新过的说明

  • 头文件不建议包含using声明,可能会和代码中的变量冲突
  • c++新风格不包含拓展名.h/.hpp/hxx就是#include<iostream>
  • C++标准库也兼容来C语言的辨准库,C语言的头文件如name.h,C++将这些文件命名为cname,即去.hc,如C语言中的ctype.h在C++中就是cctype
    cname中定义的类总能在命名空间std中找到. 而如果使用name.h文件,则需要额外记哪些是从C语言继承的,哪些是C++特有的,一般使用cname的形式

预处理器preprocessor

在编译前预处理源码,如#include就会用头文件的内容代替#include

可以使用预处理的功能确保头文件只被引入一次,即头文件保护符功能
整个程序每个头文件的保护符必须唯一,通常以头文件中类的名字来构建保护符的名字,以确保其唯一性,为了避免和程序的其他实体发生名字冲突,一般把预处理变量的名字全部大写.

//没有定义SALES_DATA_H 则包含下面内容; 第二次include时已经定义了就不包含了
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data{
      std::string bookNo;
};
#endif

其他

C++对C的拓展

命名空间(见下)

结构体struct可以定义函数体

C++比C有更严格的类型转换

在C++,不同类型的变量一般是不能直接赋值的,需要相应的强转

typedef enum COLOR{ GREEN, RED, YELLOW } color;
int testN(){

	color mycolor = GREEN;
	mycolor = 10;
	printf("mycolor:%d\n", mycolor);
	char* p = malloc(10);
	return 0;
}

c语言可以编译上面代码,但是c++报错

(base) cndaqiang@macmini day1$ g++ main.cpp
main.cpp:36:12: error: assigning to 'color' (aka 'COLOR') from incompatible type 'int'
        mycolor = 10;
                  ^~
main.cpp:38:8: error: cannot initialize a variable of type 'char *' with an rvalue of type 'void *'
        char* p = malloc(10);
              ^   ~~~~~~~~~~
2 errors generated.

需要我们严格写成

	mycolor = (color) 10;
	char* p = (char*) malloc(10);

三目运算符

  • c语言的三木运算符返回值为数据值
  • c++返回的是变量本身(引用),是左值,可以被赋值(a>b?a:b)=100;

const

c++不必const创建内存区域,(在编译阶段,编辑器悄悄优化了代码,没有创建内存,实现了宏定义的效果,称为常量折叠). c中一定要创建空间.
c++只有在声明const变量为extern或者取地址操作是,编辑器才会分配内存
推荐在c++中使用const代替宏定义(#define):const有作用域(如在特定函数内部),const有类型,可以进行编译器安全检查

  • c中const全局变量默认为外部链接,不同文件不能重名
  • c++中const全局变量默认为内部链接,不同文件可以重名
    也就是说,在A文件中const int a;,在B文件中extern const int a;会报错,B无法使用a
    如果想把a变为外部属性,在定义和声明处都写extern,定义处要给具体的值extern const int a=10;

  • const修饰全局变量时c和c++都不可以修改
    c将全局const变量存在只读数据段.
    c++只有在声明const变量为extern或者取地址操作是,编辑器才会分配内存

  • const修饰局域变量时
    c存储const局域变量在栈区,可以通过指针间接修改
    c++用指针p指向const局域变量a后,*p能被改变,而输出的a*(&a)不一定相同(见下),这是编译器的优化策略,使用volatile修饰a可以关闭优化,a==*(&a)
    //编译器优化(常量折叠)情况
    const int a=10;
    int *p = (int *) &a;
    *p=100; // 通过指针指向const局域变量,局域变量本身引用不会改变,使用地址引用发现变了
    cout << "a=" << a << ",*(&a)=" << *(&a) <<endl; //(这是编译器优化的结果(常量折叠),直接讲a替换成了10,而栈区地址的值*(&a)其实已经被改变了)
    //
    const int b=a; // 使用const常量初始化,输出b量不会被改变(常量折叠)
    p=(int *) &b;
    *p=100;
    cout << "b=" << b << ",*(&b)=" << *(&b) <<endl; 
    //----------------------------------------------------
    //编译器不优化情况
    //使用基础数据类型(int)和自定义类型(struct)初始化const局域变量, 编译器不会优化
    int c=10;
    const int d=c; //
    p=(int *) &d;  //常量会被指针改变
    *p=100;
    cout << "d=" << d << ",*(&d)=" << *(&d) <<endl;
    //
    //使用`volatile`修饰可以关闭优化
    volatile const int e=10;
    p = (int *) &e;
    *p=100;
    cout << "e=" << e << ",*(&d)=" << *(&e) <<endl;

引用/别名

  • 变量名是一段内存空间的名字,引用是给空间取别名,两个名字都指向同一片内存区域
  • 引用是c++对c的重要扩充,除了指针,引用是另一种传递地址的途径,(语法比指针更简单)也存在于其他语言中
    可以说c++没有指针,虽然可以用指针(兼容c),c++用引用
    就是用于函数传递参数的,形参会创建空间(指针也有4/8字节),而传递引用类型就不用创建空间了

  • 用法type & a = b,此处的&近作标记,不是取地址
  • 引用创建时必须初始化,一旦初始化不能改变指向,常量创建时也必须初始化,
    其实本质上引用就是常指针,
    c++编译器会把type & ref = val; 转为type * const ref = &val;
    会把ref=val2;转为*ref=val2;
  • 引用必须引用合法的内存空间
int i;
int &i2 = i;      // 右边必须是一个对象, 左边的类型要与右边的对象类型一致, 可以理解为新建立个变量叫i2,不分配新内存而是将其内存地址 &i2 设置和i一样
i=10;
std::cout << i2;  //输出10
i2=20; //这是赋值操作,无法修改i2的指向. 另外,指针的赋值*p=20;,引用直接i2=20

引用传递参数和指针的对比:语法更简单

#include<iostream>
using namespace std; // using后,就可以直接用std中的变量endl了,不用std::endl
void fun_ref(int &a)
{
    a=200; //引用的方式,可以直接使用变量名
}
void fun_poi(int *a)
{
    *a=300; // 指针计算还要在用*
}
int main()
{
    int a=10;
    fun_ref(a);//引用的输入参数也不用变
    cout << "a'" << a << endl;
    fun_poi(&a);//指针要传递的是地址
    cout << "a'" << a << endl;
    return 0;
}

数组的引用

int arr[]={1,2,3,4,5};
#方法一,创建数组类型
typedef int(MY_ARR)[5];
MY_ARR &arrref=arr
#方法二,直接引用.常用
int(&arrref2)[5]=arr;
#方法三,创建引用数组类型
typedef int(&MY_ARR)[5];
MY_ARR arrref3=arr

指针的引用

char *p="123";
char* &p1 = p; //指针的引用

还是体现在函数穿参数的定义和使用更简单

#include<iostream>
using namespace std; // using后,就可以直接用std中的变量endl了,不用std::endl

void fun_ref(char* &tmp) // 这里加个&,就是说是char * 类型,也不用传递地址了
{
    char *p;
    p=(char *)malloc(64);
    memset(p,0,64);
    strcpy(p,"fun_poi");
    tmp=p; // 直接使用就好
}
void fun_poi(char* *tmp) 
{
    char *p;
    p=(char *)malloc(64);
    memset(p,0,64);
    strcpy(p,"fun_poi");
    *tmp=p;
}
int main()
{
    char *a=NULL;
    fun_ref(a);
    cout << "a'" << a << endl;
    fun_poi(&a);
    cout << "a'" << a << endl;
    //
    return 0;
}

数据类型

类型

算术类型

类型 尺寸 范围
char 1 个字节(1 Byte = 8 bit) -128 到 127 或者 0 到 255
unsigned char 1 个字节 0 到 255
signed char 1 个字节 -128 到 127
wchar_t 2 个字节 1 个宽字符
wchar16_t 2个字节 Unicode 宽字符
wchar32_t 4 个字节 Unicode 宽字符
int 4 个字节 -2147483648 到 2147483647
unsigned int 4 个字节 0 到 4294967295
signed int 4 个字节 -2147483648 到 2147483647
short int 2 个字节 -32768 到 32767
unsigned short int 2 个字节 0 到 65,535
signed short int 2 个字节 -32768 到 32767
long int 8 个字节 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
signed long int 8 个字节 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
unsigned long int 8 个字节 0 到 18,446,744,073,709,551,615
float 4 个字节 精度型占4个字节(32位)内存空间,+/- 3.4e +/- 38 (~7 个数字)
double 8 个字节 双精度型占8 个字节(64位)内存空间,+/- 1.7e +/- 308 (~15 个数字)
long double 16 个字节 长双精度型 16 个字节(128位)内存空间,可提供18-19位有效数字。
bool true/false  

类型转换

向对象进行赋值

int i;
i=3.14; // i的值为3
double pi=i; //pi的值为3.0

自面值常量literal

常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。
常量可以是任何的基本数据类型,可分为整型数字、浮点数字、字符、字符串和布尔值。
常量就像是常规的变量,只不过常量的值在定义后不能进行修改。

整型

  • 20 十进制, -42中的-不在字面值内,代表对字面值42取负
    通过后缀指定类型 - 20u,20U unsigned无符号整数 - 20l,20L long长整数 - 20ll,20LL long long整数 - 20ul unsigned long 无符号长整数
  • 024 八进制
  • 0x16 十六进制
    std::cout << 20 <<" "<< 024 <<" " << 0x14 << std::endl;,输出20 20 20

浮点数

  • 1.234
    后缀指定类型: fF float型, lL longdouble, 示例 1.234f

字符和字符串

  • 'a' 字符字面值
    前缀指定类型:u,U,L,u8分别代表char16_t,char32_t,wchar_t,char
  • "hello world" 字符串字面值,编译器在字符串结尾添加空字符'\0',因此实际长度比内容对1
    'A'"A"的区别是'A'是字符,"A"是字符数组,包含A\0

转义序列

转义序列 含义
\\ \ 字符
\' ’ 字符
\" ” 字符
\? ? 字符
\a 警报铃声
\b 退格键
\f 换页符
\n 换行符
\r 回车
\t 水平制表符
\v 垂直制表符
\ooo 一到三位的八进制数
\xhh . . . 一个或多个数字的十六进制数

bool型 - true - false

类型别名

给类型起个别名, 可以用别名替代原类型名进行使用,但是不是简单换为之前的样子

typedef int in1, in2;   // in1,in2  都是int的别名
in1 ji;
in2 j2;
j2=10;
typedef int *p; // p是int*的同义词

c++11可以使用别名声明来定义类型的别名

using SI=Sales_item;

复合类型或常量的别名可能会产生意想不到的后果, 这里不能简单替换

typedef char *pstring; // pstring是指向char的指针
const pstring cstr=0;   //  const pstring是常数指针类型, 等价于const *chat cstr
// 上一条就不能简单替换为const char * 
const pstring *ps; // ps是是个指针,指向“常数指针”
const char * ps2; // 这里的ps2是指针,指向常变量 

auto类型

c++11标准,根据初值(表达式)内容分析所属类型,因此一定有初值

auto  a=i+j; //根据 i+j的类型推断出a的类型

auto会忽略掉顶层const,同时底层const则会保留下来

const int ci=10;  // ci顶层const,常变量
auto e=&ci; // e是底层const,是指向整型常量的指针

auto也可以和引用一起用

auto &g=ci; 
const auto &j=42; // 可以为常量, 指向表达式的常量引用

decltype

c++11标准,很具表达式类型判断类型,单不赋初值

decltype(f()) sum=x; //sum的类型为函数f的返回类型

如果是引用的类型,则必须在定义时给出初值

const int ci=0, &cj=ci;
decltype(ci) y=ci; //   必须给y赋初值

另外decltype((变量名))双层括号的结果永远是引用,而decltype(变量名)只有在变量是引用是才是引用类型

变量

定义

类型说明符 变量名1,变量名2,变量名3=value,...;

如果类型说明符是类,或者其他库中的命名空间也是如此,如

int a;
Sales_item item; //    类
std::string book("abcdefg") // 库类型std::string

初始化的方式

int i = 0;
int i = {0};
int i{0};   // 列表初始化, 需要C++11标准支持,即编译参数为 g++ -std=c++11
int i(0);

声明

c++将声明和定义区分开. 声明使程序了解变量的类型,定义则是创建变量.
变量只能被定义一次,可以多次声明.

声明示例

extern int i;
//如果用extern的语句又赋来初值,则视为定义,不再是声明
extern int i(3);

指针

指针是一种对象,值是指向不同的对象,可以随时更改其值即指向不同的对象,引用只是起一个名字,且只能指向一个对象.
指针的声明用*指针名,声明的类型必须同将来指向的对象的类型名一致

int i;
int *pi = &i;
std::cout << *pi;  //用*返回指针指向的对象的值
std::cout << pi;  //不加*返回指针的内容,即相应对象内存地址,等价于 std::cout << &i
std::cout << &i;  //返回i的地址, 即pi的内容
std::cout << &pi;  //返回pi的地址

解引符号*作用到指针上就等价于指向的对象本身,可以进行赋值等操作,
在声明时*用于定义变量是指针*,指向指针的指针**,指向(指向指针的指针)***
*是运算符, int *pi,int* pi,int * pi是等价的
cndaqiang: *代表该对象的内容, int *p,表明p的内容是个整型,即p是指针,后面调用这个指针的内容也要*p, 也可以理解为int*定义的是整型指针
&提取地址符号

int i;
int *pi = &i;
*pi=100;    // 可以这样修改原始变量空间
std::cout << i;

空指针,不能把整型等类型直接赋值给指针, 空指针if ( pi )条件返回false

pi=NULL
pi=0  //    可以赋值为0,等价于NULL,但不能赋值1,2...以及整型变量
i=0
pi=i // 这是错误的
pi=1 // 这是错误的

void类型的指针,可以指向任意类型的对象,但是他和普通指针不一样,只能用于指针比较、作为函数的输入或输出、或者赋予另一个void型指针. 不能操作void指向的对象, 也不能了解对象的类型

void *vp=&i;
std::cout << vp;
//std::cout << *vp; //错误用法

指向指针的指针,使用多个*进行定义/声明,使用多个*去取出存储的对象

int i;
int *pi = &i;
int **ppi = &pi;
int ***pppi = &ppi;
//可以继续无限套娃
i=10;
std::cout << i << *pi << **ppi << ***pppi ;

对指针进行引用,不是创建新的指针,而是指向想应指针的引用,引用后的使用和被引用的指针相同,就是起个别名而已

int ***&rp=pppi;
std::cout << i << *pi << **ppi << ***pppi << ***rp;

常变量const

定义

const int i=20;

常量的引用类型必须其引用的对象相同.
第一种例外, 初始化常量引用时允许用任意表达式作为初值,只要该表达式的结果可以转换成引用的类型即可,编译器会把计算结果保存到一个临时量对象,然后引用指向此临时量.但是非常量的引用不可以引向表达式

const int i=11;
int i2=10;
const int &ri=i;
const int &ri2=i2; // 表达式可以转换也可以
const int &ri3=34; // 表达式可以转换也可以
int &r4=ri*2; //错误语法, 非const的引用是不可以的

默认的常量只能被本文件访问,在声明和定义时添加extern允许全局访问

extern const int i=256; //定义
extern const int i ; //头文件中进行声明

底层const:对象的内容可以被改变, 即指向常变量的指针也必须加const进行定义

const int *ip;    //底层const,可以改变ip的值, ip指向常变量

顶层const:对象的内容不可以被改变

  • 常指针,指针指向固定的指针
  • 常变量,不能改变值
    int *const pc=&i; // 顶层const, pc指向&i,不可以被改变
    const int c=42;   //顶层const, 常变量
    

引用常变量也要加上const

const int &ri = i;

常量表达式:在编译过程就能得到计算结果的表达式

const int max_kpoint=256;     //    常量表达式
const int limk=max_kpoint+1;  //常量表达式,编译时就可以确定的值是常量表达式
const int sz=get_size();      // 运行时才能得到该常量的具体数值

c++11新标准规定允许将变量声明为constexpr类型,以便有编译器验证变量的值是否是常量表达式.
constexpr支持的类型都是字面值类型,不能是用户自定义的类型.
constexpr声明中如果定义了指针,则仅对指针有效, 即顶层const

const int *ip;    //指向整型常量的指针
constexpr int *ip;    //指向整型的常量指针

数组

数组的大小固定,如果不清楚元素的确切个数,建议使用vector
定义数组时必须指明数组的类型,不允许使用auto关键词根据初始判断类型.
数组的元素应为对象,因此不存在引用的数组. ?应该是元素不可以是引用,但可以被引用
使用a[i]下标访问

int a[10]; //10个元素的整数数组
int *b[10]; //10个元素数组,数组每个元素的类型为整型的指针

定义和初始化

int a[3]={0,1,2};
int b[ ]={0,1,2};// b[3]={0,1,2}
int c[5]={0,1,2}; // 缺省有默认值, a[5]={0,1,2,0,0}
string d[3]={"hi"}; //string也可以
char a[]={'C','+','+'};
char a[]={'C','+','+','\0'}; //包含显示的空字符
char a[]="C++"; //字符数组特殊,可以这样初始化,包含显示的空字符`\0`,此时如果指定a的大小应至少为4
int *pi[10]; //指针数组
int &pr[10]; //错误,元素不能为引用
int a[2][3]={
      {1,2,3},
      {4,5,6}
};
int a[2][3]={1,2,3,4,5,6};

引用数组,指向数组的指针

int arr[10];
int (&arrRef)[10] = arr;
int (*Parray)[10]=&arr;

操作

数组不可以直接拷贝和赋值,数组名一般表示位置a+4则是第5个元素的位置

int a2[]=a; //错误
a2=a;//错误

指针和数组

在很多用到数组名字的地方,编译器会自动替换为指向数组首元素的指针

string nums[]={"one","two","three"};
string *p=&nums[0];
//等价于
string *p=nums;

[略]迭代器

运算

  • i=f1()+f2(),f1f2的执行顺序未知
  • 算术运算符+-*/%加减乘除取余
  • 逻辑<,<=,>,>=,==,!=,&&,||
  • 允许赋值a=b=0,即b=0;a=b
  • 前置计算++i返回加完的i,
    后置计算i++返回加之前的i(这种返回的是运算之前的内容需要额外的存储,除非必要时不建议)
  • 后置递增运算符优先级高于解引用运算符*pbeg++等价于*(pbeg++),可以用于循环提取元素
  • 条件运算符,条件?真返回的表达式:否返回的表达式
    cond?expr_true:expr_false;
    
  • sizeof(type);sizeof expr 不计算表达式具体的值,仅返回对象/表达式结果占用的空间
  • 逗号运算符,,执行左边的计算,执行右边的计算,最后返回左边的值

位运算符

运算符 操作 示例
& p&q,p和q全为1时返回1,否则返回0  
| p&q,p或q至少一个为1时才返回 1  
^ 异或运算, p&q,p和q相同返回1,不同返回0  
~ 取反~q  
<< 二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)  
>> 二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃  

使用异或运算可以找到特殊数据,如

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素@LC

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int len = nums.size();
        if ( len == 1 ) return nums[0];
        int result = nums[0]^nums[1];
        for ( int i = 2 ; i < len ; i++ )
        {
            result ^=  nums[i];
        }
    }
};

命名空间namespace

避免重名问题,使用::使用同名变量,命名空间中的名称可以是符号常量、变量、函数、结构、枚举、类和对象等等

创建命名空间

  • 命名空间只能全局范围内定义
  • 命名空间可嵌套命名空间
  • 命名空间是开放的,可以随时把新的成员加入已有的命名空间中.
    即命名空间可以拆成多块,声明和实现可以分离(可以在前面声明,又在后面实现)
  • 无名命名空间,等价于c中的static修饰静态全局变量,属于内部链接属性,仅在当前文件可用

示例

#include<iostream>
using namespace std; // using后,就可以直接用std中的变量endl了,不用std::endl
namespace A{
    int a=10;
}
namespace B{
    namespace A{
        int a=110;
    } // 命名空间嵌套
    int b=120;
    void fun(int i); //这里仅进行定义
}
//随时补充命名空间
namespace A{ int b=20;}
namespace B{
    void fun(int i)
    {
        cout << "Input para is " << i <<endl;
    }
    void fun() //同名函数参数类型/参数顺序/返回值也可以,类似Fortran中的interface实现的同名函数重构,C++喜欢翻译为函数重载,都是Function Overloading
    {
        cout << "Fun w/o para" <<endl;
    }
}
int a=-10;
void test(){
    B::fun(B::A::a);
    B::fun(A::a);
    int a=1010;
    B::fun(a); // 直接用a按照局域>全局去寻找
    B::fun(::a); // ::并且不指出名字,直接从全局找
    using B::fun; // using后,就可以直接用名字了
    fun(10);
    fun();//支持函数重载
}

使用命名空间

  • 命名空间重命名namespace newName=oldName;
  • 使用命名空间的所有变量using A,则A中的所有变量都可以直接用变量名使用
  • using A::a,可以直接用a替代A::a.在相同作用域内,不能using不同命名空间中相同变量名的变量,也不能和现有声明的变量冲突
    using的函数支持函数重载,会参数类型/参数顺序决定具体调用形势

语句

while

while (condition)
      statement;
while (condition)
{
      statement1;
      ...;
      statement2;
}

读入不确定数量

      int i,sumi;
      sumi=0;
      while(std::cin >> i)
      {
          sumi=sumi+i;
      }
      std::cout << "Input sum is:" << sumi <<std::endl;

do while

do
      expr;
while(condition);

for

for ( int val=1; val <= 10; ++val)
{
      statement;
}

先判断条件,再执行第一次循环,即初始设置不符合条件也不会执行

int n=10;
//这里的 i < n 是每一次循环后根据新的n重新判断的
for ( int i = 1; i< n; ++i)
{
      statement;
      n=n-1
}

if

if (condition) {
      statement;
}
else {

}

switch

switch (var) {
      case value1:
            expr;
            expr;
            break;
      case value2:
            expr;
            expr;
            break;
      case ...
      default: /* 可省略,默认情况*/
            expr;
            break
}

如果不加break,则可以在随后一个条件后加break以及统计语句,实现多种情况的统计

跳转

  • break终止循环while,do while,for,switch
  • continue,进入下一个循环,适用于跳到while,do while, for的下一个循环
  • goto跳转到同一函数内的另一条语句
    goto label;
    label: expr;
    
  • return可以直接跳出整个函数体的执行,即使套了再多层的循环也会终止

错误捕获

throw略

try略

函数

  • 参数之间逗号隔开,就算类型相同也要隔开int fun(int a, int b)
  • 函数最外层作用域中的局域变量不能使用与函数形参一样的名字?
  • 函数内定义的对象尽在函数的作用域内可见
  • 仅在第一次定义初始化,后续处理的值保留,就是Fortran的SAVE属性,用static定义
  • 调用之前要声明,可以多次声明,可以写在头文件中,函数体和调用可以不在同一个文件,可以分开编译

参数

  • 传递对象名时,是传值调用,形参和实惨相互独立
  • 传递的内容是指针时,修改了指针指向的内存区域,可以实现更改函数外的对象
  • 因为数组不能被拷贝,所以传递的通常是数组的地址(即以指针的方式进行传递)
  • main的参数
    int main(int argc, char *argv[]) { ... }
    

    argv[0-...],

  • 默认参数,可以调用时可以省略实参
    typedef string::size_type sz;
    string screen( sz ht=24, sz wid = 80, char backgrnd = ' ' )
    

函数重载

同一作用域内几个函数名字相同单参数不同时,称为函数重载, 就和Fortran的interface的同名函数重构功能

定义

注意结束定义处的分号

struct 类名
{
      类体;
      //c只能定义成员变量(函数指针变量可以),不能定义成员函数
      //c++可以定义成员函数
};    // 此处分号不可缺少

也可以通过class创建

class 类名
{
      类体
};    // 此处分号不可缺少

struc和class的区别在于:对于structure在定义似一个访问说明符之前的成员是public的,而class是private的. 如果希望成员都是public时使用structure,希望成员时private使用class

创建对象

类名 对象(变量)名;
//c中是struct 类名 对象(变量)名;
//c++中不用加struct

使用

对象.对象

标准库

iostream

c++为定义任何的输入输出(IO)语句,使用标准库来实现. 标准库的4个IO对象.

  • std::cin 标准输入,istream类型的对象
  • std::cout 标准输出,ostream类型的对象
  • std::cerr 标准错误,
  • std::clog 一般信息,

输入和输出以流的方式进行,比如std::cout << "Input num is:" << i <<std::endl;,就是把 "Input num is:",i,std::endl依次流向标准输出.

输出运算符<<左侧是ostream类型的对象(如std::cout),右侧是对象要打印的值,运算的返回值是左侧的对象,因此std::cout << A << B;等价于(std::cout << A) << B;,以及std::cout << A; std::cout << B;

输入运算符>>同理返回左侧的对象, std::cin >> A >> B;等价于(std::cin >> A) >> B;,以及std::cin >> A; std::cin >> B;

std::cin >> A 读入结束或者读入数据和A的类型冲突时,返回False,可以配合while,if等进行联合使用,读入不定数量的输入/对读入进行判断, Unix系统的文件结束输入为ctrl+d(输入完成,先回车在ctrl+d)

std::endl功能一是结束本行,可以用”\n”达到换行的功能, std:endl还有将内存缓冲区的内容刷到设备的作用. 类似于fortran中的flush(6), 便于找到bug的地方.

这些对象都来自std的命令空间namespace, 因此在使用的时候要用作用域算符::调用

#include<iostream>
int main()
{
      std::cout << "hello world" << std::endl;
      std::cout << "Input:" ;
      int i;
      std::cin >> i;
      std::cout << "Input num is:" << i <<std::endl;
      return 0;
}

string

因为某些历史原因,也为了与C兼容,所以C++语言中的字符串字面值char类型并不是标准库类型string的对象.切记, 字符串字面值与string是不同的类型

#include <string>
using std::string;
main()
{
      return 0;
}

定义和初始化

string是一个类,初始化这个类的对象, 通过()进行(操作计算出来的)直接初始化,使用=进行拷贝初始化

string s1; //默认初始化,s1是一个空串
string s2(s1); // s2是s1的副本,
string s3("hello"); //s3是hello的副本
string s2=s1; //  同上
string s3="hello"; //同上
string s4(n,'c'); //s4由n个'c'组成的串

操作

操作 含有
os<<s 将s写到输出流os当中,返回os, 即std::cout << si << sj;
is>>s 输入流is中读取字符串赋给s,字符串以空白分隔,返回is, 即std::cin >> si >> sj;
getline(is,s) getline就是从输入流is中读取一行(到换行符位置)赋给s(不包括换行符)
读到末尾时会返回False,其他情况返回true
>>是等价的,也可以和while连用
s.empty() s为空返回true,否则返回 false
s.size() 返回s中字符的个数
s[n] 返回s中第n个字符的引用,位置n从0计起,string和vector的下标只能用于访问,不能修改
s1+s2 返回s1和s2连接后的结果
s1=s2 用s2的副本代替s1中原来的字符
s1==s2
s1!=s2
如果s1和s2中所含的字符完全一样,则它们相等;
string对象的相等性判断对字母的大小写敏感
<,<=,>,>= 利用字符在字典中的顺序进行比较,且对字母的大小写敏感

for

C++11支持使用for处理string对象的每个字符,配合ispinct()等函数可以进行统计

      string s="hello123";
      for ( auto tmp : s)
            std::cout << tmp << "\n";

也可以用for改变

      string s="hello123";
      for ( auto &tmp : s) // 这里tmp是引用,指向了相应位置的地址
            tmp=toupper(tmp);

vector

  • C++标准要求vectory能在运行时高效添加元素,因此和其他语言的定义时指明具体容量以加速计算相反,通常先建立空的vector,后期动态添加
    #include<vector>
    using std::vector;
    

    vector是类模版,(类、函数)模版不是类,而是为生成类或函数便携的一份声明,
    vector为例,提供的信息供编译器创建类(实例化).这里其实是创建各种类型的组成个一个的类.

    vector<int> ivec; \\生成一个int元素组成的类,并创建相应的对象ivec
    vector<vector<string>> ivec;  \\向量元素是vectory对象
    

初始化

列表初始化是C++11的特性.
注意{}()的区别

操作

添加元素

v2.push_back(i);   //加到末尾
//插入到第一个元素前面
v2.insert(v2.begin(),8);//在最前面插入新元素8。 
v2.insert(v2.begin(),3,8);//在最前面插入新元素{8,8,8}。 
//由于v.end() == v.begin()+v.size()
v2.insert(v2.end(),8);//在末尾插入新元素8。 

批量赋值
//赋值v[0-3](不包括v.begin()+4),到b[4-]
//复制到第k个(下标是k-1),v.begin()+k
copy(v.begin(),v.begin()+4,b.begin()+4);

v.begin()+v.size()等价于v.end(),完整复制即copy(v.begin(),v.end(),b.bengin())

排序
#include <algorithm>
std::sort (v.begin(), v.end());
删除元素

C++ STL vector删除元素的几种方式(超级详细)

//删除第j个元素
auto iter = nums.erase(nums.begin()+j-1);
//删除num[j]
auto iter = nums.erase(nums.begin()+j);

函数传递

使用引用和传递地址的方式可以改变vectory的值, 传值调用无法改变原先的值

// 传值
int fun1( vector<int> a)
{
std::cout << "fun1" ;
 for ( int i=0; i<a.size(); i++)
{
        std::cout << a[i];
}
std::cout << std::endl;

return 0;
}

// 引用传入
int fun2( vector<int>& a)
{
std::cout << "fun1" ;
 for ( int i=0; i<a.size(); i++)
{
        std::cout << a[i];
        a[i]=199;
}
std::cout << std::endl;

return 0;
}
int main()
{
std::cout << "hi" <<std::endl;

vector<int> v={0,1,2,3,4,5,6,7,8,9,10};
int j;
j=fun1(v);
//引用传入才可以改变值
j=fun2(v);
std::cout << v[0] << std::endl;

map

C++map用法@W3Cschool
类似字典,使用关键词进行索引,关键词可以是多种类型

#include <map>
std::map<关键词类型,值类型> 变量名;
//如
std::map<int,int> mymap;

添加元素

std::map < int , std::string > mapPerson;
mapPerson.insert(pair < int,string > (1,"Jim"));
mapPerson.insert(std::map < int, std::string > ::value_type (2, "Tom"));
mapPerson[3] = "Jerry";

基本操作

  • begin() 返回指向 map 头部的迭代器
  • clear() 删除所有元素
  • begin() 返回指向 map 头部的迭代器
  • clear() 删除所有元素
  • count() 返回指定元素出现的次数,返回整数, 只能是0或1
  • empty() 如果 map 为空则返回 true
  • end() 返回指向 map 末尾的迭代器
  • equal_range() 返回特殊条目的迭代器对
  • erase() 删除一个元素
  • find() 查找一个元素
  • get_allocator() 返回map的配置器
  • insert() 插入元素
  • key_comp() 返回比较元素key的函数
  • lower_bound() 返回键值>=给定元素的第一个位置
  • max_size() 返回可以容纳的最大元素个数
  • rbegin() 返回一个指向map尾部的逆向迭代器
  • rend() 返回一个指向map头部的逆向迭代器
  • size() 返回map中元素的个数
  • swap() 交换两个map
  • upper_bound() 返回键值>给定元素的第一个位置
  • value_comp() 返回比较元素value的函数

[略]迭代器

其他

set

#include<set>
        int len = nums.size();
        if ( len == 1 ) return false;
//        set虽然可行但不快
        set<int> set_nums;
        for ( int i = 0; i < len  ; i++)
        {
          //插入新元素, 并判断是否存在
          pair<set<int>::iterator,bool>  pair1=set_nums.insert(nums[i]);
          if ( pair1.second == false ) return true;
        }     

本文首发于我的博客@cndaqiang.
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!



评论


广告

目录

广告
访客数据