C++初阶:基础语法

本文介绍了C++中的命名空间,包括其定义、嵌套和使用方式,以解决命名冲突问题。接着讨论了C++的输入输出,如iostream和命名空间std的使用。然后解释了函数缺省参数的概念和分类,以及如何在实际应用中使用。此外,详细阐述了函数重载的概念和规则。最后,深入探讨了引用,包括引用的定义、特性、常引用及其在函数参数和返回值中的应用,强调了引用与指针的区别和效率差异。

目录

C和C++的区别

1、命名空间

1.1 命名空间的定义

命名空间嵌套

1.2命名空间的使用

命名空间的使用有三种方式:

2、C++输入&输出

3、缺省参数

3.1  缺省参数的概念

3.2  缺省参数分类

1、全缺省参数

2、半缺省参数

缺省参数的应用

4、函数重载

函数重载的定义

5、引用

5.1 引用的概念

5.2 引用特性

5.3 常引用

5.4 引用的应用

1、作函数参数

2、做返回值

5.5 传值传引用效率对比

5.6 引用和指针的区别

6、内联函数

6.1 概念

7.2 特性

7.3

7、auto关键字(C++)

7.1 类型别名思考

7.2 auto简介

7.3 auto 的使用

1、auto与指针和引用结合起来使用

2、在同一行定义多个变量

3、auto 不能推导的情况

8、基于范围的 for 循环

8.1 范围for的语法

8.2 范围for的使用条件

1、 for循环迭代的范围必须是确定的

2. 迭代的对象要实现++和==的操作

9、指针空值 nullptr


C和C++的区别

1、C是面向过程的语言,是结构化和模块化的语言,适合处理较小规模的程序,考虑的是如何通过一个过程(函数)对输入进行处理得到输出;

C++是面向对象的语言,主要特征是“封装、继承和多态”。封装隐藏了实现细节,是得代码模块化;派生类可以继承父类的数据和方法,扩展已经存在的模块,实现了代码复用;多态则是“一个接口,多种实现”,通过派生类重写父类的虚函数,实现了接口的重用。

2、C和C++动态管理内存的方法不一样,C是使用malloc/free,而C++除此之外还有new/delete关键字。C++是通过在C的基础上进行扩展,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。故C++兼容C绝大多数的特性。也暗含着C++的基础语法也是为了弥补C语言的某些缺陷。

3、比如,C++中有引用,C中不存在引用的概念。(下面)

1、命名空间

在C中可能会出现命名冲突问题,定义的全局变量、函数有可能与C/C++库文件以及项目中团队成员写的文件中的变量发生命名冲突的问题

定义的全局变量rand和库中的rand发生冲突。

解决:

在C++中引入了命名空间,使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。

1.1 命名空间的定义

在C中不同函数内部可以使用相同的变量名,它们被域所隔离,只在当前的函数域内起作用,域分为局部域和全局域,它们影响变量的使用和生命周期。

1、命名空间,即定义一个域来定义变量、函数或类型,使用这些变量时需在其名称前面加上特定的标识,对标识符的名称进行本地化处理以避免命名冲突或名字污染。

2、定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员

//test是命名空间的名字,一般开发中是用项目名字做命名空间名。
namespace test {
    //1.变量
    int rand = 0;
    //2. 函数
    int Add(int a, int b) {
        return a+b;
    }
	//3.结构体
    struct Node {
    int val;
    struct Node* next;
    };
}

命名空间中只能定义变量或函数并进行初始化,不可以进行其他操作。

命名空间嵌套

namespace N1 {
	int a = 0;
	int b = 0;
	int Add(int a, int b) {
		return a + b;
	}
	namespace N2 {
		int c = 0;
		int d = 0;
		int Sub(int c, int d) {
			return c - d;
		}
	}
}

1、同一个工程中允许存在多个相同名称的命名空间,当命名空间重名时,两个同名的命名空间会被合并为一个工作空间。

2、一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中

1.2命名空间的使用

在命名空间中定义变量,相当于将变量封锁起来,只能使用特定的方式进行访问。

在变量名前加上空间名和 : :,即告诉编译器使用该命名空间中的变量。当然,域作用限定符并不是C++新增的C语言中同样可以使用

void func()
{
	int a = 11;	//局部变量
}
int a = 22;		//全局变量
int main()
{
	int a = 33;
	printf("%d\n", a);		//33 优先寻找函数内的局部变量
	printf("%d\n", ::a);	//22 ::域作用限定符 直接进入全局寻找
	return 0;
}

: : 域作用限定符 前不写空间的名称,就表示使用全局范围的变量。

命名空间的使用有三种方式:

加命名空间名称及作用域限定符 : :

namespace test 
{
	int rand = 0;
}
int main() 
{
    printf("%d", test::rand);
}

在变量名前加上空间名和: :,即告诉编译器使用该命名空间中的变量。

使用using namespace 命名空间名称 引入

using 展开命名空间 影响查找规则

using namespace test;

int main()
{
    rand = 1;
    Add(1, 2);
    struct Node n1;
    return 0;
}

这种写法将该命名空间中的所有变量或者函数都展开,好处是非常方便但坏处是使整个命名空间失效,可能造成命名冲突。相当于取消隔离,所以要慎用。

使用using将命名空间中某个成员引入

using namespace test::b;
using namespace test::rand

int main()
{
      printf("%d\n", test::a);
      printf("%d\n", b);
      return 0
}

指定引入单独的空间成员,只展开常用的成员,相对来说更加安全可靠。

注意:

在实际开发的项目中使用指定命名空间访问,常用部分展开使用,小的程序,日常练习中可以全局展开

2、C++输入&输出

#include <iostream>  //C++标准输入输出流文件
using namespace std;
int main()
{
    int a;
    cin>>a;
    //1. 全部展开
	cout << "hello world" << endl;
	//2.指定使用
    std::cout << "hello world" << std::endl;
    //3.部分展开
    using std::cout;
	using std::endl;
	cout << "hello world" << endl;
    return 0;
}
  • 1、使用cout标准输出对象(控制台)cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std。
  • 2、cout和cin是全局的流对象endl是特殊的C++符号,表示换行输出,他们都包含在包含<iostream >头文件中。
  • 3、 <<是流插入运算符,>>是流提取运算符
  • 4、使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。C++的输入输出可以自动识别变量类型
  • 5、实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识
#include <iostream>
using namespace std;
int main()
{
 int a;
 double b;
 char c;
 
 // 可以自动识别变量的类型
 cin>>a;
 cin>>b>>c;
 
 cout<<a<<endl;
 cout<<b<<" "<<c<<endl;
 return 0;
}

cout,cin可以自动识别类型,不需要格式控制符,也可以根据变量的值自动控制小数点位数。如果想实现控制域宽或小数位数,最好使用 prinf 和 scanf 。cin 和 scanf 都存在缓冲区遗留回车字符\n的问题。

cin 不能接受空格

3、缺省参数

3.1  缺省参数的概念

缺省参数是声明或定义函数时为函数的参数指定一个缺省值(默认值)。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。

void func(int a = 0)
{
	cout << a << endl;
}
int main()
{
	func();		//没有传参时,使用参数的默认值
	func(1);	//传参时,使用指定的实参

	return 0;
}

3.2  缺省参数分类

1、全缺省参数

void Func(int a = 10, int b = 20, int c = 30)
{
  cout<<"a = "<<a<<endl;
  cout<<"b = "<<b<<endl;
  cout<<"c = "<<c<<endl;
}

2、半缺省参数

void Func(int a = 10, int b = 20, int c = 30) {
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	cout << "c=" << c << endl << endl;
}
int main()
{
    //从右往左连续缺省
	Func(); // 不传参
	Func(1); // 传 a=1
	Func(1, 2); // 传 a=1,b=2
	Func(1, 2, 3); // 传 a=1,b=2,c=3
    //错误写法
    Func(,2,);
    Func(,,3);
    Func(1,,3);
	return 0;
}

注意:
1. 半缺省参数必须从右往左依次来给出,不能间隔着给

//1. 全缺省
void Func(int a = 10, int b = 20, int c = 30) {}
//2.从右往左连续缺省
void Func(int a, int b = 20, int c = 30) {}
//3. 
void Func(int a, int b, int c = 30) {}
//错误形式
void Func(int a = 10, int b, int c = 30) {}
void Func(int a = 10, int b, int c) {}

2. 缺省参数不能在函数声明和定义中同时出现

3. 缺省值必须是常量或者全局变量

缺省参数的应用

struct Stack
{
	int* a;
	int top;
	int capacity;
};
//初始化
void StackInit(struct Stack* ps,int defaultCapacity = 4)
{
	//ps->a = (int*)malloc(sizeof(int*) * 4);	//空间写死
	ps->a = (int*)malloc(sizeof(int*) * defaultCapacity);	//缺省参数使用
	//...
	ps->top = 0;
	//ps->capacity = 4;
	ps->capacity = defaultCapacity;

}
int main()
{
	Stack st1;
	StackInit(&st1, 100);//开辟100

	Stack st2;
	StackInit(&st2);//缺省,默认开辟

	return 0;
}

capacity的大小,如果确实实际可以传实参,不确认大小,使用默认参数

4、函数重载

函数重载的概念:函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。

注意:返回类型不同不算重载。因为仅靠返回类型无法在调用时区分重载的函数。

函数重载的定义

//1、参数类型不同
int Add(int left, int right)
{
    cout << "int Add(int left, int right)" << endl;
    return left + right;
}
double Add(double left, double right)
{
    cout << "double Add(double left, double right)" << endl;
    return left + right;
}
//2.参数个数不同
void f()
{
    cout << "f()" << endl;
}
void f(int a)
{
    cout << "f(int a)" << endl;
}
// 3、参数类型顺序不同
void f(int a, char b)
{
    cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
    cout << "f(char b, int a)" << endl;
}

5、引用

5.1 引用的概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间

//类型 & +引用变量名(对象名) = 引用实体
void TestRef()
{
  int a = 10;
  int& ra = a;//<====定义引用类型
    
  printf("%p\n", &a);	//0037F74C
  printf("%p\n", &ra);  //0037F74C
}

 注意:引用类型必须和引用实体同种类型的

5.2 引用特性

  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,再不能引用其他实体
//1.
int a = 10;
//int& ra;	//该条语句编译时会出错

//2.
int& b = a;
int& c = a;
int& d = a;

//3.
int e = 1;
int& f = a;
f = e;	//把e赋值给f所引用的变量a

定义引用时必然要初始化,不然别名就没有意义了。

一个变量可以取多个别名,但一个别名被使用后便不可以指代其他变量,只能是赋值操作。

5.3 常引用

常变量是具有常属性的变量,那么常引用就是具有常属性的引用。常引用会涉及到权限的问题

//权限放大
const int a = 0;
int& ra = a;	//该语句编译时会出错,a为常量 ,不可更改

//权限缩小
int b = 0;
const int rb = b;	// b可以更改,但是rb不可以改变

//权限相同
const int c = 0;
const int& rc = c;

引用const修饰的常变量时,如果引用不加const,那么就造成了权限扩大显然时不允许的。权限不允许扩大但是可以不变和缩小,在用引用做参数时,可以加const修饰防止实参被修改。

int fun()
{
	int n = 0;

	return n;
}
int main()
{
	int& ret = fun(); //错误 传回的是临时变量
    const int& ret = fun();

    int x = 0;
	double& y = x;//不能接受 类型不匹配 类型变化产生临时变量
	const double& y = x;

	return 0;
}

1、临时变量具有常性 需要const修饰为常量接受

2、类型不匹配 类型变化产生临时变量 ,临时变量具有常性

5.4 引用的应用

1、作函数参数

void Swap(int x, int y) {     //传值
    int tmp = x;
    x = y;
    y = tmp;
}
void Swap(int* px, int* py) { //传址
	int tmp = *px;
	*px = *py;
	*py = tmp;
}
void Swap(int& rx, int& ry) { //传引用
	int tmp = rx;
	rx = ry;
	ry = tmp;
}
int main() {
	int x = 0, y = 1;
	Swap(&x, &y);
	cout << x << " " << y << endl;
	Swap(x, y);
	cout << x << " " << y << endl;
	return 0;
}

第一种传值调用显然是不行的,传址调用和传引用的区别在于前者通过地址访问到实参x,y,后者通过引用实参避免了实参的临时拷贝。

typedef struct Node
{
	struct Node* next;
	int val;
}Node, *PNode;

//typedef struct Node* PNode;
void SLTPushBack(Node** pphead, int x);  // C

//引用 优化
void SLTPushBack(Node*& phead, int x);  // CPP
void SLTPushBack(PNode& phead, int x);

引用传的是地址,可以解决C中二级指针的麻烦使用,形参改变实参也改变

2、做返回值

int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;	//7
	return 0;
}

注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

函数的返回值

1、除字符串返回 其他都会暂时存储到 临时变量常性

2、函数返回值出栈销毁,接受的 是临时变量

3、接受后 临时变量 释放

引用做返回值

1、引用做返回值 是返回 返回值的别名

2、 减少拷贝 不在产生临时变量

3、如果返回的不是静态/全局 ,会发生 栈销毁了 接受别名的变量 再次访问 出错

5.5 传值传引用效率对比

#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestTransferRefAndValue() {	
	A a;
	size_t begin1 = clock();
    for (size_t i = 0; i < 1000000; i++)    
        TestFunc1(a);
	size_t end1 = clock();
	size_t begin2 = clock();
	for (size_t i = 0; i < 1000000; i++)
		TestFunc2(a);
	size_t end2 = clock();
	cout << "TestFunc1(A a):" << end1 - begin1 << endl;
	cout << "TestFunc2(A& a):" << end2 - begin2 << endl;
}
int main() {
	TestTransferRefAndValue();
	return 0;
}

 传引用和传指针差不多,每次调用都访问的是同一块空间,而传值每次调用都会开辟一样大的空间所以传引用的效率比传值高很多,数据越大对性能的提升越大。引用可以作输出型参数或输出型返回值,就是达到形参改变外面的实参的目的。

5.6 引用和指针的区别

 引用和指针的不同点:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  2. 引用在定义时必须初始化,指针没有要求
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  4. 没有NULL引用,但有NULL指针
  5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  7. 有多级指针,但是没有多级引用
  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  9. 引用比指针使用起来相对更安全

6、内联函数

6.1 概念

inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

7.2 特性

  1. 内联函数是一种以空间换时间,省去调用函数开销,故一般十行以上或有递归迭代的函数不宜使用内联函数。
  2. 内联函数仅是对编译器的一种建议,编译器自动优化,如果不符上述要求,编译器会放弃优化。
  3. 内联函数不建议声明和定义分离,内联函数不会调用就没有函数地址分离会导致链接失败。

7.3

宏的优缺点?

优点:
        1.增强代码的复用性。
        2.提高性能。
缺点:
        1.不方便调试宏。(因为预编译阶段进行了替换)
        2.导致代码可读性差,可维护性差,容易误用。
        3.没有类型安全的检查 。

实现ADD宏函数

 C++有哪些技术替代宏?

  1. 常量定义 换用const enum
  2. 短小函数定义 换用内联函数

7、auto关键字(C++)

7.1 类型别名思考

  1. 类型难于拼写
  2. 含义不明确导致容易出错

 使用typedef给类型取别名确实可以简化代码,但是typedef有会遇到新的难题:

指针常量必须初始化

7.2 auto简介

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它,大家可思考下为什么?

C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得出

int TestAuto()
{
    return 10;
}
int main()
{
    int a = 10;
    auto b = a;
    auto c = 'a';
    auto d = TestAuto();
    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;
    cout << typeid(d).name() << endl;  

return 0;
}

  • 使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型
  • 因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型

auto e; //无法通过编译,使用auto定义变量时必须对其进行初始化

7.3 auto 的使用

1、auto与指针和引用结合起来使用

int a = 10;
//定义指针
auto pa = &a;
auto* pa = &a;
//定义引用
auto& ra = a;

使用auto定义指针可以再auto后面加*也可以不加,通过初始化的内容都能确定指针的类型。但引用必须要加&,不然无法确定。

2、在同一行定义多个变量

auto a = 1, b = 2;
auto c = 3, d = 4.0;  // 该行代码会编译失败,因为c和d的初始化表达式类型不同

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

3、auto 不能推导的情况

3.1 auto不能作为函数的参数

// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}

3.2 auto不能直接用来声明数组

int a[] = {1,2,3};
auto b[] = {4,5,6};	//err

8、基于范围的 for 循环

8.1 范围for的语法

//C
for (int i = 0; i < len; i++) {
    cout << arr[i] << endl;
}
//C++
for (auto e : arr) {
    cout << e << endl;
}

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。

for (int i = 0; i < len; i++) {
    arr[i] *= 2;
} 
for (auto& e : arr) {
    e *= 2;
}

范围 for 循环返回的对象是数组元素值的拷贝,所以若要写入数组元素的话,需要使用引用。

8.2 范围for的使用条件

1、 for循环迭代的范围必须是确定的

void TestFor(int arr[]) {
	for (auto e : arr) {
		cout << e << endl;
	}
}

对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供

begin和end的方法begin和end就是for循环迭代的范围

2. 迭代的对象要实现++和==的操作

9、指针空值 nullptr

在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:

void TestPtr()
    {
    int* p1 = NULL;
    int* p2 = 0;
}

NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。

注意:

1. 在使用 nullptr 表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。

2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值