2022/9/15 初始版
2022/10/24 新增24、25两点,第7、21点有补充
2022/12/6 新增26、27两点,新增遇到的错误总结6点
#include
#include
#include
using namespace std;
#define T(A) #A
#define TEST(X) printf(#X);
#define TEST1(X) printf("This is \#X \n");
#define TEST2(X) printf("This is '#X' \n");
#define TEST3(X) printf("This is "#X" \n");
int main(){
int a = 10;
double b = 10.5;
float c = 15.5f;
string str = "Hello";
cout << T(a) << endl; //a
cout << T(2.2) << endl; //2.2
cout << T(2.5f) << endl; //2.5f
cout << T("abcd") << endl; //abcd
TEST(10); //10
cout << endl;
TEST1(15); //This is #X
TEST2(20); //This is '#X'
TEST3(25); //This is 25
return 0;
}
#include
#include
#include
using namespace std;
#define TEST(Name) c##Name
#define TEST1(a, b) int(a##b##a)
#define P(format, ...) printf(format, ##__VA_ARGS__);
//在__VA_ARGS__宏前面加上##:当可变参数个数为0时,##会把多余的“,”去掉,否则编译会出错
#define P2(format, ...) printf(format, __VA_ARGS__);
int main(){
string cTest = "Test ##";
cout << "cTest = " << TEST(Test) << endl; //Test ##
cout << TEST1(10, 24) << endl; //102410 没有int()也是一样的结果
P("Hello ##__VA_ARGS__\n");
//P2("Hello __VA_ARGS__\n"); //Error
return 0;
}
当宏参数是另一个宏的时候,只要宏定义里有"#" 或者是 “##” 的地方,宏参数不会展开,即只有当前宏会生效,参数里的宏不生效(包括系统和库函数中的宏定义)
#include
#include
#include
using namespace std;
#define T (5)
#define TEST(a) #a
#define TEST1(a, b) int(a##e##b)
int main(){
cout << "TEST(T) = " << TEST(T) << endl; //套自定义的宏定义 TEST(T) = T
cout << "TEST(INT_MAX) = " << TEST(INT_MAX) << endl; //套库函数中的宏定义 TEST(INT_MAX) = INT_MAX
//cout << "TEST1(T, T)" << int(TEST1(T, T)) << endl; //error
cout << "TEST1(5, 5) = " << TEST1(5, 5) << endl; //TEST1(5, 5) = 500000
return 0;
}
解决方案:加一层中间转换宏
在类A里面声明了一个成员函数void test(),但是没有在类内进行定义(类内定义默认为内联函数)。在类外定义时需要使用双冒号进行定义:
class A {
public:
A();
~A();
void test();
};
A::A() {}
A::~A() {}
void A::test() {
std::cout << "Test ::" << std::endl;
}
在VC++里面,可以调用Windows API里的函数,在API函数前面加双冒号即可
// 获取进程ID
DWORD dwProcessId = ::GetCurrentProcessId();
表示引用成员函数及变量
std::cout << "Test ::" << std::endl;
gcc中,函数名为snprintf。VC中成为_snprintf,安全函数为_snprintf_s
//VC
#include
int main(int argc, char *argv[])
{
char buf[50];
std::string str = "1234567890abcd";
printf("%d\n", _snprintf_s(buf, 10, str.c_str())); // -1
printf("%s\n", buf); //1234567890
return 0;
}
//_snprintf_s的第二个参数表示:会向buf中写入10个字符,并且不会再字符串末尾添加'\0',如果字符串长度超过10,返回-1标志可能导致的错误
//Linux 同C++11
#include
int main(){
char buf[50];
std::string str = "1234567890abcd";
printf("%d\n", snprintf(buf, 10, str.c_str())); //14
printf("%s\n", buf); //123456789
return 0;
}
//snprintf的第二个参数表示:向buf中写入10个字符,包括'\0',且返回实际的字符串长度。str.size() = 14
//Windows
//VC6.0 不支持long long的定义以及cout不支持64位长整型
__int64 a; //Win32 64位长整型
printf("%l64d\n", a);
printf("%l64u\n", a);
//Linux
long long a;
printf("%lld\n", a);
printf("%llu\n", a);
占位符 | 说明 | 占位符 | 说明 |
---|---|---|---|
%a,%A | 浮点数、十六进制数和p-记数法(C99) | %c | 字符char |
%C | 一个ISO宽字符 | %d | 有符号十进制整数int |
%ld,%Ld | 长整型数据long | %hd | 短整型数据 |
%e,%E | 浮点数、e/E-记数法 | %f | 单精度浮点数float,十进制 |
%nf | n表示精确到小数位后n位 | %g,%G | 根据数值不同自动选择%f或%e |
%i | 有符号十进制数(同%d) | %o | 无符号八进制整数 |
%p | 十六进制形式输出指针 | %s | 对应字符串char *(%s==%hs==%hS输出窄字符串) |
%S | 对应宽字符串wchar_t *(%ws==%S输出宽字符串) | %u | 无符号十进制整数 |
%x | 无符号十六进制整数(2f) | %#x | 无符号十六进制整数(0x2f) |
%X | 无符号十六进制整数(2F) | %#X | 无符号十六进制整数(0X2F) |
%% | 打印一个百分号 | %lld | 用于INT64或long long |
%llu | 用于UINT64或者unsigned long long | %llx | 用于64位16进制数据 |
%n | 什么也不输出。将在%n之前输出的字符数存储到指针所指的位置。printf(“1234%n”, &num); //不输出 printf(“%d”, num);//num = 4 | %m | 打印errno值对应的出错内容 |
%:格式说明的基本符号,不可缺
-:左对齐输出,默认右对齐
0:指定空位补0
m.n:m是域宽(对应输出项在输出设备上所占字符数),n是精度(默认为6)
#include
int main(){
int a = 0x355;
int b = 0x5;
//a全部输出355 超过两位,实际输出
printf("%2x\n", a);
printf("%02x\n", a);
printf("%-2x\n", a);
printf("%.2x\n", a);
printf("%2x%2x\n", b, a); // 5355 数据不足两位,前补空格
printf("%02x%02x\n", b, a); //05355 数据不足两位,前补0
printf("%-2x%-2x\n", b, a); //5 355 数据不足两位,候补空格
printf("%.2x%.2x\n", b, a); //05355 效果同%02x
return 0;
}
在C中:
没有nullptr
NULL是一个宏,被定义为空指针
#define NULL ((void *)0)
在C++中:
在进行函数重载时,NULL容易出现二义性。C语言可以隐式转换,比如函数重载了int和char两个类型的,传入了NULL,就会出现二义性,编译就会报错。因为C本身不支持函数重载,所以纯C不会出现这个问题
//NULL的定义
#undef NULL
#if defined(__cplusplus) //C++预处理器宏
#define NULL 0
#else
#define NULL ((void *)0)
#endif
C++中不能将(void *)类型的指针隐式转换成其他指针类型
nullptr不是整型类,也不是指针类型,但是能转换成任意指针类型。实际上是std::nullptr_t
#if define(__cplusplus) &&__cplusplus >= 201103L
#ifndef _GXX_NULLPTR_T
#define _GXX_NULLPTR_T
typedef decltype(nullptr) nullptr_t;
#endif
#endif // C++11
总结:在C++中尽量使用nullptr,在C中使用NULL
把WinSock2.h放到Windows.h前面
new分配内存并且返回指向该内存块的指针
在该指针指向处调用对应的构造函数构造对象
内置数据类型或者组合数据类型的对象的值如不加()是默认不进行初始化的。但是类类型对象将用默认构造函数进行初始化(如string或自定义类)
int *p1 = new int; //未初始化
int *p2 = new int[]; //未初始化
int *p3 = new int[10](); //带括号意味着调用了构造函数,进行了初始化
注意在指针数组、vector<自定义对象类型指针>等,插入一个 new了的对象时,要每次遍历完都delete掉不用的对象,或者最后编写一个循环的释放内存的函数去释放对象
是一个很少见的调用约定,一般不建议使用
编译器不会给它增加初始化和清理代码,而且不能使用return返回返回值,只能插入汇编返回结果
此调用约定必须跟__declspec同时使用
如:定义一个求和程序
__declspec(naked) int add(inta, int b);
void TcharToChar(const TCHAR *tchar_, char *&char_) {
/*int len = 0;
len = WideCharToMultiByte(CP_ACP, 0, tchar_, -1, NULL, 0, NULL, NULL);
WideCharToMultiByte(CP_ACP, 0, tchar_, -1, char_, len, NULL, NULL);*/
if (tchar_ != nullptr) {
int tchlen = wcslen(tchar_);
int chlen = WideCharToMultiByte(CP_ACP, 0, tchar_, tchlen, NULL, 0, 0, 0) + 2;
char_ = new char[chlen];
if (char_) {
memset(char_, 0, chlen);
WideCharToMultiByte(CP_ACP, 0, tchar_, tchlen, char_, chlen, 0, 0);
}
}
}
void getFileName(TCHAR pszFullPath[MAX_PATH]) {
char *pch = nullptr;
char *temp = { 0 };
TcharToChar(pszFullPath, temp);
pch = strrchr(temp, '\\');
cout << "FileName:" << pch + 1 << endl;
}
参考《C和指针》第八章 数组
数组名的值是数组第一个元素的指针常量
数组名不是指针,但大部分情况下编译器会把数组名隐式转换成一个指向数组首地址的指针
隐式转换成指针常量:
int arr[5] = {0, 1, 2, 3, 4, };
cout << "arr[2] = " << arr[2] << endl; //2
cout << "*(arr + 2) = " << *(arr + 2) << endl; //2
//实际上,arr[2]就是被隐式转换成了*(arr + 2)
但是在大部分情况下,也有例外:
对数组名使用sizeof运算符时,会得到整个数组所占内存的大小
int arr[10] = {0};
cout << "sizeof(arr) = " << sizeof(arr) << endl; //sizeof(arr) = 40 10(数组大小) * 4(int所占字节数)
对数组名使用取地址符时,得到的是数组的地址(虽然和数组首地址的值一样,但是两个不同的概念)
int arr[10] = {0};
cout << "&arr = " << &arr << endl; //&arr = 0x7ffe9454f800
cout << "arr[0] = " << arr[0] << endl; //&arr = 0x7ffe9454f800
cout << "arr = " << arr << endl; //arr = 0x7ffe9454f800
注意 数组的地址 和 数组首地址 的区别
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, };
cout << "sizeof(arr) = " << sizeof(arr) << endl; //40
cout << "&arr = " << &arr << endl; //&arr = 0x7ffcd2be1270
cout << "&arr + 1 = " << &arr + 1 << endl; //&arr + 1 = 0x7ffd5b0b0428 这里是首地址加了40,指向了arr的最后一个元素的下一个元素(尾后指针)
cout << "arr = " << arr << endl;//rr = 0x7ffd5b0b0400
cout << "arr + 1 = " << arr + 1 << endl; //rr = 0x7ffd5b0b0404
使用sizeof获取数组元素个数
int arr[] = {1, 2, 3, 4, 5, 6, 7, 0, };
cout << "sizeof(arr) / sizeof(arr[0]) = " << sizeof(arr) / sizeof(arr[0]) << endl; //8
const修饰类的成员函数,一般放在函数体后
void funcName(int a, ...) const;
常函数的实现也要带const关键字
不能修改类的成员变量
不能调用类中没有被const修饰的成员函数(只能调用常成员函数)
流操作算子(成员函数) | 作用 | 流操作算子(成员函数) | 作用 |
---|---|---|---|
dec | 默认,十进制输出整数 | hex | 十六进制输出整数 |
oct | 八进制输出整数 | fixed | 普通小数形式输出浮点数 |
scientific | 科学计数法输出浮点数 | left | 左对齐,宽度不足将填充字符添加到右边 |
right | 默认,右对齐,宽度不足填充字符添加到左边 | setbase(b) | 设置输出整数时的进制,b=8/10/16 |
setw(w) | 输出/输入宽度为w个字符 | setfill© | 宽度不足时用c填充(默认空格) |
setprecision(n) | 在使用非fixed且非scientific方式输出的情况下,n为有效数字最多的位数,如果超过n,小数部分四舍五入或自动变为科学计数法,输出并保留以供n为有效数字。fixed方式和scientfix方式下,n是小数点后面应保留的位数 | setiosflags(flag) | 将某个输出格式标志置为1 |
resetiosflags(flag) | 将某个输出格式标志置为0 | boolapha | 把true和false输出为字符串 |
noboolalpha | 默认。把true和false输出为1和0 | showbase | 输出表示数值的进制的前缀 |
noshowbase | 默认。不输出表示数值的进制的前缀 | showpoint | 输出小数点 |
noshowpoint | 默认。只有小数部分存在才显示小数点 | showpos | 非负数值中显示+号 |
nonshowpos | 默认。非负数值中不显示+号 | skipws | 默认。输入时跳过空白字符 |
noskipws | 输入时不跳过空白字符 | uppercase | 十六进制中使用A~E。输出前缀则输出0X,科学计数法输出E |
nouppercase | 默认。十六进制使用a~e。输出前缀则输出0x,科学计数法输出e | internal | 数值的正负号在指定宽度内左对齐,数值右对齐,中间由填充字符填充 |
setiosflags()算子实际上是一个库函数。flag标志可以是如下值:
标志 | 作用 | 标志 | 作用 |
---|---|---|---|
ios::left | 输出数据在本域宽范围内左对齐 | ios::right | 输出数据在本域宽范围内右对齐 |
ios::internal | 数值的符号位在域宽内左对齐,数值右对齐,中间由填充字符填充 | ios::dec | 设置整数的基数为10 |
ios::oct | 设置整数的基数为8 | ios::hex | 设置整数的基数为16 |
ios::showbase | 强制输出整数的基数 | ios::showpoint | 强制输出浮点数的小数点和尾数 |
ios::uppercase | 以科学计数法E 和 以十六进制输出字母时以大写表示 | ios::showpos | 对整数显示+号 |
ios::scientific | 浮点数以科学计数法格式输出 | ios::fixed | 浮点数以定点格式(小数)输出 |
ios::unitbuf | 每次输出后刷新所有流 | ios::stdio | 每次输出后清除stdout,stderr |
多个标志位可以用 | 运算符链接,表示同时设置:
cout << setiosflags(ios::left | ios::showbase | ios::stdio) << endl;
int atoi(const char *str);
把字符串转成整数。会扫描参数中的所有字符串,跳过前面的空白字符
如果不能转为int或字符串本身为空,返回0。注意如果字符串前面是数字,后面为非数字,前面的数字会被转换成功,反之非数字在前就会失败
#include
#include
#include
using namespace std;
int main(){
string s1 = "1234";
string s2 = "1234abcd";
string s3 = "abcd1234";
string s4 = "abcd";
cout << "1234 = " << atoi(s1.c_str()) << endl; //1234
cout << "1234abcd = " << atoi(s2.c_str()) << endl; //1234
cout << "abcd1234 = " << atoi(s3.c_str()) << endl; //0
cout << "abcd = " << atoi(s4.c_str()) << endl; //0
return 0;
}
在缺省的C locale下,wcout不可以直接输出中文,需要将locale设为本地语言才能输出中文
wcout.imbue(locale(locale(), "", LC_CTYPE));
//或者
wcout.imbue(locale("", LC_CTYPE));
//或者
wcout.imbue(locale("")); //这个会改变wcout的所有locale设置,不建议使用。
wcout << L"11223344:" << 11223344 << endl; //11223344:11,223,344
//或者
wcout.imbue(locale("chs")); //指定chs即MS936 CodePage映射,系统将UTF-16码流转为GB2312码流输出到控制台焕春
ostream& ends (ostream& os);
template <class charT, class traits>
basic_ostream<charT,traits>& ends (basic_ostream<charT,traits>& os);
_CRTIMP inline basic_ostream<char, char_traits<char> >&
__cdecl ends(basic_ostream<char, char_traits<char> >& _O)
{
_O.put('\0');
return (_O);
}
ends实际上是输出了一个’\0’字符
在C++使用ends时,都是在缓冲区插入’\0’,但不会马上刷新缓冲区。但是在Windows下会输出空格,Linux下则什么也不显示。这是两个系统对’\0’处理方式不同造成的
使用flush可以直接刷新缓冲区而不额外输出
一个用于调试程序的宏,仅在DEBUG模式下生效,Release模式下会被自动忽略
#include
void assert(int expression);
用于在函数开始时检验传入的参数的合法性。判断expression是否为真,如果结果为假,输出诊断消息并终止程序(向stderr打印错误信息,调用abort终止程序),避免导致严重后果以及方便查找错误
缺点:频繁调用会极大影响程序性能,增加额外开销。无需调试时应使用#define NDEBUG
#define NDEBUG
#include
...
注意事项:
使用_tcscpy时,不论时unicode编码还是其他编码,都无需求改代码
以上函数功能皆为:复制一个字符串到缓冲区
const TCHAR DirPath[300] = "abcdefg";
TCHAR szTemp[305] = { 0 };
//_tcscpy_s(szTemp, 305, DirPath);//第二个参数是元素个数并非字节数,直接写305只是在TCHAR为char时恰好正确,使用Unicode编码时会出错
//安全拷贝函数,DirPath的长度不能超过305-1,将DirPath拷贝到szTemp中,自动在szTemp后加上'\0'
_tcscpy_s(szTemp, sizeof(szTemp) / sizeof(szTemp[0]), DirPath);
int sprintf(char *buffer, const char *format[, argument] ...);
与printf类似,只是printf输出到控制台,而sprintf输出到字符串
返回值:成功则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。失败则返回一个负数。
int snprintf(char *restrict buf, size_t n, const char *restrict format, ...);
最多从原字符串中拷贝n-1个字符到目标串中,然后再在后面加一个’\0’。如果目标串的大小>=n,不会溢出,还是取n-1个字符。
返回值:成功返回想要写入的字符串长度(不包括’\0’),出错返回负值
C++11新标准引入。C++11以前可以使用:
将以下参数中的各个标准类型的数据转为string类型(to_wstring同理)
string to_string (int val);
string to_string (long val);
string to_string (long long val);
string to_string (unsigned val);
string to_string (unsigned long val);
string to_string (unsigned long long val);
string to_string (float val);
string to_string (double val);
string to_string (long double val);
返回一个包含val作为字符序列的表示形式的字符串对象。使用格式与printf为相应类型打印格式相同
参考自 C++ Primer(第五版)
C++ 为了解决多态问题,解析的时候会把函数名和参数合在一起生成一个中间函数的名称,C语言则不会,二者的函数名修饰方式不同,编译时会报错 “unresolved external symbol xxx”
因此在C++中引用C语言的函数和变量、包含C语言的头文件时,需要使用extern "C"以告诉编译器保持我原来的名称,不要生成用于链接的中间函数名
extern "C" void test();
C++支持分离式编译,很多时候都将声明和定义区分开来,声明使名字为程序所知,定义负责创建与名字关联的实体。
而一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。如果想声明一个变量而非定义它,就在变量名前加extern关键字
代码演示:
//testExtern.h
#pragma once
//要在除了testExtern.cpp之外的文件使用这个arr,就必须声明extern
extern char* arr;
class A {
public:
A() {}
~A() {}
void testShow();
};
//testExtern.cpp
#include "testExtern.h"
#include
#include
//定义,未赋值未初始化,直接访问也会报错
//char arr[] = "abcd"; //这和.h文件中extern的char *是两个变量 某类型的数组 != 某类型的指针
char* arr = new char[50]; //如果没有定义会编译报错:无法解析外部符号 char * arr
void A::testShow() {
memset(arr, 0, 50);
snprintf(arr, 50, "test char *");
std::cout << arr << std::endl;
}
//main.cpp
#include "testExtern.h"
#include
extern char* arr;
int main() {
std::cout << arr << std::endl; //访问时未初始化,为乱码
A a;
a.testShow();
std::cout << arr << std::endl; //和testShow结果一样
system("pause");
return 0;
}
典型的函数定义包括:返回类型、函数名字、由0或多个形参组成的列表、函数体
通过调用运算符 “()” 来执行函数,它作用于一个表达式,该表达式是函数或者指向函数的指针
“()” 之内是实参列表,用实参初始化函数形参。实参类型必须与对应的形参类型匹配
#include
//n的阶乘,非递归
int func(int n){ //n 是形参
int res = 1;
while(n > 1){
res *= n--; //n * (n - 1) * (n - 2) * ... * 1
}
return res; // ①返回语句中的值;②将控制权从被调函数转回主调函数
}
int main(){
int n;
std::cin >> n;
// 函数的调用:①实参初始化函数对应的形参;②将控制权转移给被调用函数,主调函数的执行被暂时中断,被调函数执行
int res = func(n); //调用函数,传实参
std::cout << n << "! = " << res << std::endl;
system("pause");
return 0;
}
特殊的返回类型:void,表示函数不返回任何值
函数返回类型不能是数组类型或函数类型,但可以是指向数组或函数的指针
自动对象:
局部静态对象
当有必要让局部变量的生命周期贯穿函数调用及之后的时间时,可以将局部变量定义成静态类型(static)
局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,知道程序终止销毁,在此期间即使对象锁在函数结束执行也不会对它造成影响
#include
int CountCalls(){
static int count = 0; //声明为静态的,调用结束后,这个值仍然有效,只会进行一次初始化
return ++ count; //会一直累加
}
int main(){
int calls = 0;
for(int i = 0; i < 10; ++i){
calls = CountCalls();
}
std::cout << "Calls : " << calls << std::endl; //10
}
多文件编译执行过程
.o文件在编译后就能获得,但是库文件,可执行文件都需要在链接后才能获得
源代码–>编译器–>汇编代码–>汇编器–>目标代码–>链接器–>可执行程序
编译:读取源程序(字符流) --> 进行词法语法分析 --> 转换高级语言指令为汇编代码 --> 转换为机器码 --> 生成目标文件(.obj / .o)
指针的行为和其他非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值,拷贝之后,两个指针是不同的指针,因为指针使我们可以间接地访问它所指的对象,所以通过指针可以修改它所指对象的值:
#include
void reset(int* p){
*p = 100; //修改的是ip所指对象的值
p = 0; //p的地址为0,只改变了形参
std::cout << "p = " << p << std::endl;
}
int main(){
int a = 0, b = 20;
int* p = &a; //指针p指向a的地址
int* q = &b; //指针q指向b的地址
*p = 10; //a的值改为10,指针p的值不变(还是指向a的地址)
p = q; //指针p和指针q一样指向了b,但是指针q和变量b的值都不变
reset(p);
std::cout << "p = " << p << std::endl; //这里p的地址还是和q一样
std::cout << "*p = " << *p << std::endl; // 100
return 0;
}
C程序员常使用指针类型的形参访问函数外部的对象。C++中建议使用引用类型的形参代替指针
引用的操作实际上是作用在引用所引的对象上,也就是给原本的对象起了个别名:
#include
void reset(int& p){ //使用引用无需拷贝,提高效率
p = 0;
}
int main(){
int a = 0, b = 20;
int& p = a; //引用变量p绑定了变量a,相当于给a起了个别名为p
p = 100;
std::cout << "p = " << p << std::endl; //100
std::cout << "a = " << a << std::endl;
p = b;
std::cout << "p = " << p << std::endl; //20
std::cout << "a = " << a << std::endl; //20
reset(p);
b = p;
std::cout << "p = " << p << std::endl; //0
std::cout << "a = " << a << std::endl; //0
std::cout << "b = " << b << std::endl; //0
return 0;
}
如果函数无需改变引用形参的值,最好将其声明为常量引用
常使用引用形参返回额外信息:一个函数只能返回一个值,但有时候函数需要返回多个值,这种时候除了定义一个新的数据类型包含多个成员之外,还可以给函数传入一个额外的引用实参:
int GetCount(const char ch, const std::string str, std::string& ss){
int count = 0;
for(int i = 0; i < str.size(); ++i){
if(str[i] == ch){
++count;
continue;
}
ss += str[i];
}
return count;
}
int main(){
std::string str = "aaabbbccddeeff";
std::string ss = "";
int count = GetCount('a', str, ss);
std::cout << "count = " << count << std::endl //3
<< "ss = " << ss << std::endl; //"bbbccddeeff"
return 0;
}
不允许拷贝数组,所以无法以值传递的方式使用数组参数
使用数组时通常会将其转换成指针,所以当为函数传递一个数组时,实际上传递的是指向数组首元素的指针,以下三个函数尽管形式不同,但是是等价的:
void print(const int*);
void print(const int[]);
void print(const int[10]);
int i = 0;
int arr[2] = {0, 1};
print(&i); //正确,&i的类型的int*
print(arr); //正确,j转换为int*并指向j[0]
#include
// C风格字符串,使用标记指定数组长度
void print(const char* cp){
if(cp){
while(*cp){
std::cout << *cp++ << " ";
}
}
}
int main(){
std::string str = "abcdef12345";
print(str.c_str());
return 0;
}
**数组引用形参:**引用两端的括号必不可少
void func(int (&arr)[10]){
std::cout << arr[0] << std::endl;
}
// void func1(int &arr[10]){ //error-type: 不允许使用引用的数组C/C++(251)
// std::cout << arr[0] << std::endl;
// }
传递多维数组:
void func2(int (*arr)[10]){ //指向含有10个整数的数组的指针
std::cout << arr[0][0] << std::endl;
}
void func3(int *arr[10]){ //10个指针构成的数组
std::cout << arr[0][0] << std::endl;
}
int main(){
int arr[10][10] = {
{1, 2, 3},
{4, 5, 6},
};
func2(arr);
//func3(arr); //"int (*)[10]" 类型的实参与 "int **" 类型的形参不兼容C/C++(167)
return 0;
}
处理不同数量实参的函数,C++11提供了两种主要方法:
initializer_list形参:如果函数的实参数量未知但是全部实参类型相同,可以使用它,它用于表示某种特定类型的值的数组
initializer_list<T> lst; //默认初始化,T类型元素的空列表
initializer_list<T> lst{a, b, c...}; //lst的元素数量和初始值一样多;lst的元素是对应初始值的副本;列表中的元素是const
lst2(lst); lst2 = lst; //拷贝或赋值一个initializer_list对象,不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素
C++中的set和map是红黑树实现的,要使用自定义类型的时候要在类型中重载小于号”<“。即要对自定义类型有偏序关系,这样C++才能正确构建红黑树
struct Test {
int x;
string y;
double z;
//比较运算符重载,按位置排序,必须const
bool operator <(const Test& a) const
{
return x < a.x;
}
};
//方法二
bool operator<(const Test& a, const Test& b)
{
return a.x < b.x;
}
void testDelete() {
unordered_map<int, float> m2;
m2.insert({ 1, 1.5 });
unordered_map<char, string> m3;
m3.insert({ 'a' , "aaa" });
unordered_map<int, set<Test*>> m1;
Test t1 = { 1, "1", 1.5 };
Test t2 = { 2, "2", 2.5 };
Test t3 = { 3, "3", 3.5 };
set<Test*> test;
test.insert(&t1);
test.insert(&t2);
test.insert(&t3);
// m1.insert(make_pair(2, test));
m1.insert({ 2, test });
//m1.emplace(1, test); //emplace避免产生不必要的临时变量。这里直接用insert会报错:没有与参数列表匹配的重载函数
//遍历删除元素(vector/list/map/set/deque/string等同理)
for (unordered_map<int, set<Test*>>::iterator iter = m1.begin(); iter != m1.end(); iter++) {
for (set<Test*>::iterator i = iter->second.begin(); i != iter->second.end(); /*如有插入/删除操作,这里不能递增或递减迭代器*/) {
cout << (*(i))->x << endl;
cout << "Erase " << (*(i))->x << endl;
iter->second.erase(i++); //必须在删除后立即进行递增或递减操作
//i = iter->second.erase(i++); //或者使用迭代器去接收移除元素后返回的结果(erase会返回紧随被删除元素的下一个元素的有效迭代器)。推荐这种写法
//--i; //运行时错误,不能减少值初始化的map/set迭代器
}
cout << "size: " << iter->second.size() << endl;
}
}
ErrorList 中报错但是实际上代码没有问题:
属性 — C/c++ — 命令行 — 添加 /Zm1000(或者更大的数)