C++输入输出基础、函数重载、引用、字符串基础、对齐要求
// windows中c++源文件后缀为.cpp,编译源文件后产生目标文件,后缀为.o,链接器将 目标代码文件 和 库代码(library code)、启动代码(startup code) 链接/合并,生成可执行文件.exe,unix中可执行文件为.out
#include <iostream> //提供c++输入输出流函数
#include <iomanip> //提供c++io格式控制,manipulate操作操纵
using namespace std; //使c++标准库内容可直接访问(namespace名称空间,后续详细介绍)
int main1() //c++中函数形参列表不填相当于(void)
{
cout << "输入整数:"; //cout对象的作用类似c的printf,输出内容,插入运算符<<将右侧内容插入左侧cout(输出流),输出流从程序流出
int a;
cin >> a; //cin的作用类似c的scanf,抽取运算符>>从左侧cin(输入流)抽取值赋给变量a,根据a的类型进行解释,输入流流入程序
cout << "a = " << a << endl; //<<运算符从左往右运算,cout<<"a = "将字符串插入标准输出流,cout<<...返回cout本身,继续执行cout<<a,默认根据变量a的类型转换字符串,后再次返回cout本身,继续cout<<endl,即endline换行,endl使输出换行并刷新缓冲/将字符串发送出程序
int b = 5;
(a = b) = 10; //c中a=b表达式的值为a的值(备份)5,表达式5=10报错,但在c++中a=b表达式的值为a本身,即先将b的值赋给a,返回a本身,继续执行a=10,最终a为10,b为5
cout << "输入2个整数:";
cin >> a >> b; //cin先流入a再流入b,类似scanf("%d%d",a,b),两个数字之间通过空白符号隔开
double d = 1.234567;
cout << d << endl; //相当于%g
cout << d << setprecision(5) << endl; //相当于%5g,整数加小数总共5位有效数字
cout << setprecision(5) << setiosflags(ios::fixed) << d << endl; //相当于%.5f
cout << setw(10) << b << endl; //相当于%10d
cout << setw(10) << setfill('x') << b << endl; //用x填充空位
cout << setw(10) << setiosflags(ios::left) << b << endl; //左对齐相当于%-10d,setw只对单次打印生效,setfill和setiosflags为全局设置
cout << hex << a << oct << a << dec << a << endl; //hex相当于%x,oct相当于%o,dec相当于%d
cout << setbase(16) << a << endl; //setbase设置进制基数,设为16等价于hex
char ch;
cin >> ch; //读取会跳过空白符号,相当于scanf(" %c",&ch)
string s1 = "c++使用string对象取代char[]数组表示字符串";
cout << s1;
return 0;
}
int Add(int a, int b)
{
return a + b;
}
int Add(int a, int b, int c)
{
return a + b + c;
//c++函数重载,函数名相同,形参列表不同,构成重载,在编译阶段会发生倾轧,将每种add函数更换为不同的名称
//形参列表不同是重载的充要条件,与返回值无关(因为程序无法得知用户想要哪种返回值的函数),形参的数量不同、类型不同,类型顺序不同都可以构成重载,形参的标识符不同不构成重载
}
float Add(float a, float b)
{
return a + b;
}
int main2()
{
cout << Add(1, 3) << endl; //程序根据1和3的类型(int)匹配add(int,int)函数
//cout << Add(1.0, 3.0) << endl; //1.0和3.0都为double类型,没有add(double,double)函数的时候会进行隐式类型转换,将double截断为int或者转换为float,但因为同时存在add(int,int)和add(float,float),程序不知道转哪个而报错
//同理也有int转long或double的二义性报错
return 0;
}
extern "C" int Minus(int a, int b); //在c中没有c++的倾轧,所有c函数需要包在extern "C" 语句中避免倾轧
extern "C"
{ //复合语句用大括号
int Minus(int a, int b) //函数声明和函数定义要都倾轧或都不倾轧(保持一致)
{
return a - b;
}
}
struct xy { int x; int y; };
int main3()
{
xy s1 = { 4,3 }; //c++中声明结构变量不需要写struct,结构别名和变量共享名称空间
xy s2 = { 2,3 };
xy s3 = { s1.x - s2.x,s1.y - s2.y }; //错误s3=s1-s2 结构不能直接相减
return 0;
}
xy operator-(xy s1, xy s2) //运算符重载
{
xy s3 = { s1.x - s2.x,s1.y - s2.y };
return s3;
}
int main4()
{
xy s1 = { 4,3 };
xy s2 = { 2,3 };
xy s3 = s1 - s2; //减号实际调用operator-(s1,s2)
return 0;
}
int Times(int a, int b = 5) //默认参数b
{
return a * b;
}
int main5()
{
cout << Times(2) << endl << Times(2, 3) << endl; //只传一个参数时参数2使用默认值
return 0;
}
//int Times(int a = 5, int b); 错误,默认参数必须全部写在右侧
//int Times(int a); 错误,重载和默认参数冲突,当Times(a)一个参数时程序不知道使用一个参数的函数还是默认参数的函数
int Times(int a, int b, int c = 5); //如果函数声明和函数定义分开写时,默认参数写在函数声明中,函数定义不写,如头文件中函数原型要写默认参数
int Times(int a, int b, int c) //Times(a,b,c=5)和Times(a,b=5)构成重载,但是当调用Times(a,b)时又出现二义性/冲突,所以如果使用默认参数,就只声明一次函数,如果使用重载,最好完全不使用默认参数
{
return a * b * c;
}
int main6()
{
int a = 5;
int& ra = a; //引用reference,ra被声明为a的别名,只存在于编译阶段之前,经过编译后所有ra都是a,引用没有创建空间,对ra的使用和对a的使用完全一样
ra = 50; //等价于a=50
int b = 2;
ra = b; //等价于a=b,这个语句并不会让ra转为引用b,引用不可更改
//int& rb; 错误,引用在声明时必须初始化,所以引用必须有引用对象且不能更改
int& rra = ra; //等价于 int&rra = a;
//int& (&rra) = a; 错误,引用本身并不存在(只存在于编译之前),不能声明引用的引用
//int& arr[5]; 错误,同理,引用不占空间,不能作为数组的元素
int arr[5];
int(&rarr)[5] = arr; //数组的引用,需要保持类型、元素数量一致,元素数量不能省略,将 "int 变量名[size]" 的"变量名"改为 "(&引用名)" 即可声明引用,rarr先和&结合成为引用再和int[5]结合成为数组的引用
int* ptr;
int*& rptr = ptr; //指针的引用,同理,先写 int* 变量名,再改写成int*(&引用名),括号省略
//int&* p; 错误,同样因为引用不占空间,没法用指针去指向引用
return 0;
}
int Divide(int& ra, int& rb) //形参使用引用,不创建空间
{
return rb ? ra / rb : 0;
}
int main7()
{
int i1 = 3, i2 = 2;
cout << Divide(i1, i2) << endl; //函数调用时int&ra=i1,ra即是i1本身,不同于函数调用将实参的值(备份)传给形参
//cout << Divide(5, 3) 错误,int&ra无法引用常量
const double& r1 = i1; //声明常量引用,实际内部隐式声明了一个double变量赋值(double)i1,再声明引用r1,所以r1依然不占空间,但内部创建了一个变量
const double& r2 = 5; //同样隐式声明了变量,值为5.0
const double& r3 = i1 + 5; //隐式声明了(double)(i1+5)
//在某些实现中,引用通过 type* const 变量名 来实现,即实际引用声明了一个不可修改指向的指针,通过该指针可以修改指向的值,涉及引用的函数调用也会经过编译后变成主调函数传递变量地址,被调函数通过指针接收,以此来减少用户主动使用指针的情况
const int i3 = 4; //c++中const声明必须初始化
//错误 int* p = &i3; //c++中不能对const声明的常量进行任何直接、间接修改
int i4 = 4;
const int* p = &i4; //const常量p不可修改,但不影响i4
p = &i3; //和c一样可以更改p的指向
return 0;
}
void mainreference2()
{
int a = 10;
//int& ref = a; int* const ptr = &a;引用的实质是创建一个const指针(不能改变指向),通过*ptr来表示变量本身
const int& ra = a;
cout << "&a = " << &a << endl;
cout << "&ra = " << &ra << endl; //const引用并非任何时候都生成一个临时变量
const int& ra2 = a + 2; //当引用对象不是左值时,const引用会生成一个临时变量
cout << "&ra2 = " << &ra2 << endl;
const int& ra3 = ra2; //ra2是不可修改的左值
cout << "&ra3 = " << &ra3 << endl; //ra3和ra2地址相同,因为类型相同,且ra2是左值(无论可否修改),所以引用对象是左值就不会生成临时变量,地址相同
double d = 1.0;
const int& rd = d; //当引用对象的类型和引用类型不同时,如果能够类型转换则不报错、const引用会生成一个临时变量,这里rd为临时变量(int)d的引用
cout << "&d = " << &d << endl;
cout << "&rd = " << &rd << endl;
}
const int& reference1(int& ri)
{
//函数的引用传参和返回引用同声明引用的用法,形参ri为实参变量的引用而非实参值,即int& ri = 主调函数变量
return ri; //返回的虽然是const引用,但引用与变量类型相同并且变量是左值,所以const返回依然是变量本身,只是附加了只读的限制,实际通过类似const int*来实现
}
#include <string> //提供string类相关的方法
int mainother1()
{
auto cars = 15; //c++中auto关键字用法不同于c,c++中用auto代替类型名typename,使声明的变量初始化为右值的类型和值,15是int常量,所以auto cars变为int类型,值为15
auto iou = 150.37f; //iou为float类型
auto level = 'B'; //c++中字符常量为char类型,所以level为char,值为66
auto crat = U'\U00002155'; //char32_t类型,c++规定char32_t至少为32位,用来表示单字符32位的字符集,使用U'\U表示,后面跟8位16进制数。而char16_t类型用于表示单字符16位的字符集,用u'\u小写u表示Unicode字符
auto fract = 8.25f / 2.5; //8.25f为float,2.5为double,将float转换为double统一类型进行运算,fract为double类型
auto sss = short(20) + short(2); //整型提升,长度小于int的类型会在运算时被自动转换为int或unsigned int,c++规定int为系统运算最快的整数类型,提升不会造成精度损失,所以两个short先转换为int运算结果为int,auto根据结果的类型声明变量的类型,所以变量类型为int而非short
int a1 = 10;
int a2 = 010; //八进制
int a3 = 0x10; //十六进制
double d1 = 2345.6;
double d2 = 2.3456e3; //e3乘以10的三次方/小数点右移三位
char ch = 65;
cout << ch << endl; //cout输出char类型时解释为字符而非编码,结果为A
enum MyEnum //枚举,定义符号常量
{
one = 1,only=1,two //符号常量的值可重复,不显式赋值则比前一个符号常量大1
};
cout.put(97); //作用类似putchar()
ch = cin.get(); //作用类似getchar()
if (EOF == ch)
cout << ".get()会返回eof,就像getchar一样,输入流读到eof会设置eof位禁止继续读取,可通过cin.clear()重置流状态恢复输入";
cin.get(ch); //.get(char& )将下一个读取的字符赋给主调函数的变量,读到eof不会赋值,会设置eof位禁止继续读取,可通过cin.clear()重置流状态恢复输入
cin >> ch; //cin的>>抽取运算符会跳过空白符号,所以cin>>ch会将空白符号以外的第一个字符赋值给ch
char temp[80];
cin.getline(temp, 80); //从输入流中读取一行,存储到参数1char*的地址上,参数2限制最多读取的数量-1个字符,这里为最多读取79个字符,保留最后一位给\0,如果读取到换行符会使用\0替代换行符结束读取,如果读取80个字符依然没有读取到换行符则会设置失效位(failbit)阻断接下来的输入,再次调用getline等任何读取的函数都不再进行读取/挂起,可通过cin.clear()重置流状态恢复输入
cin.get(temp, 80); //作用类似getline,但会将换行符保留在输入中。当读取到空行时会设置失效位
if (cin)
cout << "任何读取产生的错误,通过设置失效位都会导致cin不能进行读取,这时cin在关系表达式中会转换为false,而.getline()等返回cin的函数可直接放在if、while测试条件中通过cin转换的bool值判断是否读取成功";
else
cin.clear(); //cin转换为true表示读取正常,转换为false时需要.clear()重置状态才能继续读取
cin.get(temp, 80).get(); //如果一行长度小于80,则这句和getline()效果相同,cin.get(temp, 80)返回cin,继续调用.get()读取换行符
string strobj; //定义字符串对象
getline(cin, strobj); //全局函数getline,参数1传cin,参数2传string对象,将读取一行输入存储到string对象中
cin >> strobj; //读取一个单词保存到strobj中
"字符串";
L"宽字符字符串";
u"char16_t或unicode字符串";
U"char32_t字符串";
R"(生字符串,不使用转义字符,如\n表示为\和n两个字符)"; //生字符串以R开头,后跟"( 内容 )",即用 R"( 标识生字符串的开始,用 )" 标识字符串的结束,如果需要在生字符串内部使用)"连用的情况,可在开头"和(之间添加一些字符,在结尾的)和"之间添加对应的字符
R"ab(这个生字符串就以R"ab(开头,结尾添加对应的字符顺序相同而非对称)ab";
//c++标准允许main()函数不写返回值,不写等同于return 0
if (10 == a1)
cout << "value==variable ,将常量放在左侧,这样如果少写等号变为赋值语句时会报错提示";
//因为抽取运算符>>跳过空白符号,所以如果程序的所有输入都使用抽取运算符,即可免去处理换行符的麻烦,每次使用>>和.get().getline()等交替时都需要处理换行符
int i1;
while (cin >> i1)
cout << "成功抽取到int类型值:" << i1 << endl;
cout << "抽取遇到非数字字符,cin判断为false退出循环,在调用cin.clear()之前无法再通过.get() >> 等方法读取字符" << endl;
//cin>>抽取失败时不会改变变量的值,且cin在条件判断中转换为false,需要.clear()清除错误状态才能继续读取
return 0;
}
void mainforeach()
{
int arr[] = { 1,2,3,4,5,6,7 };
for (int i : arr) //foreach
{
i++; //i仅为迭代目标中元素的备份,i改变不会影响元素的值
}
for (int& i : arr) //获得每个元素的引用
{
cout << i++ << endl; //引用递增即元素本身递增
}
for (auto& i : { 3,6,8,1,2 }) //复合字面量,auto自动匹配类型,i会逐一引用
{
cout << i << endl;//i作为字面量/常量元素的引用(const int&),不可修改
}
}
void mainalignof()
{
cout << "alignof(char)" << alignof(char) << endl; //alignof(type)返回类型的对齐要求,char的对齐要求为1
cout << "alignof(int)" << alignof(int) << endl; //int的对齐要求为4,这不仅要求int变量的位置必须为4的倍数,也要求变量的大小必须为4的倍数
cout << "alignof(double)" << alignof(double) << endl; //double的对齐要求为8,即double类型必须放置在8的倍数位置,且double类型大小必须占用8的倍数的空间
struct MyStruct
{
char c; //1字节
double d; //8字节
int i; //4字节
};
cout << "alignof(MyStruct)" << alignof(MyStruct) << endl; //结构的对齐要求为8,即位置必须为8的倍数且必须占用8的倍数的空间
cout << "sizeof(MyStruct)" << sizeof(MyStruct) << endl; //这个结构的大小为24,远大于1+4+8,分析:首先结构必须在8的倍数的位,所以第一个成员char在8n位占用一个字节(对齐要求为1),下一个成员double对齐要求8所以不能放在8n+1位,后移至8n+8位放置,前七个字节空出,double占用了8个字节,下一个成员int根据对齐要求可以放在8n+16位置,占用4个字节,但结构的对齐要求为8,所以需要再占4字节空位凑够8的倍数,所以总共占用了3*8字节
}